Added Appc spec validation utility. Review: https://reviews.apache.org/r/37310
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/ab75d1d6 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/ab75d1d6 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/ab75d1d6 Branch: refs/heads/master Commit: ab75d1d662e1506550f277e6403d1bd25aa060d4 Parents: a070de3 Author: Jiang Yan Xu <[email protected]> Authored: Mon Aug 10 11:15:46 2015 -0700 Committer: Jiang Yan Xu <[email protected]> Committed: Thu Aug 13 15:16:22 2015 -0700 ---------------------------------------------------------------------- src/Makefile.am | 3 + .../containerizer/provisioners/appc/spec.cpp | 104 +++++++++++++++++ .../containerizer/provisioners/appc/spec.hpp | 54 +++++++++ .../containerizer/appc_provisioner_tests.cpp | 114 +++++++++++++++++++ 4 files changed, 275 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/ab75d1d6/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index f8e54d2..d664df6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -458,6 +458,7 @@ libmesos_no_3rdparty_la_SOURCES = \ slave/containerizer/mesos/launch.cpp \ slave/containerizer/provisioner.cpp \ slave/containerizer/provisioners/appc/paths.cpp \ + slave/containerizer/provisioners/appc/spec.cpp \ slave/resource_estimators/noop.cpp \ usage/usage.cpp \ v1/attributes.cpp \ @@ -713,6 +714,7 @@ libmesos_no_3rdparty_la_SOURCES += \ slave/containerizer/linux_launcher.hpp \ slave/containerizer/provisioner.hpp \ slave/containerizer/provisioners/appc/paths.hpp \ + slave/containerizer/provisioners/appc/spec.hpp \ slave/containerizer/isolators/posix.hpp \ slave/containerizer/isolators/posix/disk.hpp \ slave/containerizer/isolators/cgroups/constants.hpp \ @@ -1619,6 +1621,7 @@ mesos_tests_SOURCES = \ tests/zookeeper_url_tests.cpp \ tests/common/http_tests.cpp \ tests/common/recordio_tests.cpp \ + tests/containerizer/appc_provisioner_tests.cpp \ tests/containerizer/composing_containerizer_tests.cpp \ tests/containerizer/docker_containerizer_tests.cpp \ tests/containerizer/docker_tests.cpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/ab75d1d6/src/slave/containerizer/provisioners/appc/spec.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/appc/spec.cpp b/src/slave/containerizer/provisioners/appc/spec.cpp new file mode 100644 index 0000000..15a3257 --- /dev/null +++ b/src/slave/containerizer/provisioners/appc/spec.cpp @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stout/os/stat.hpp> +#include <stout/protobuf.hpp> +#include <stout/strings.hpp> + +#include "slave/containerizer/provisioners/appc/paths.hpp" +#include "slave/containerizer/provisioners/appc/spec.hpp" + +using std::string; + +namespace mesos { +namespace internal { +namespace slave { +namespace appc { +namespace spec { + +Option<Error> validateManifest(const AppcImageManifest& manifest) +{ + // TODO(idownes): Validate that required fields are present when + // this cannot be expressed in the protobuf specification, e.g., + // repeated fields with >= 1. + // TODO(xujyan): More thorough type validation: + // https://github.com/appc/spec/blob/master/spec/types.md + if (manifest.ackind() != "ImageManifest") { + return Error("Incorrect acKind field: " + manifest.ackind()); + } + + return None(); +} + + +Option<Error> validateImageID(const string& imageId) +{ + if (!strings::startsWith(imageId, "sha512-")) { + return Error("Image ID needs to start with sha512-"); + } + + string hash = strings::remove(imageId, "sha512-", strings::PREFIX); + if (hash.length() != 128) { + return Error("Invalid hash length for: " + hash); + } + + return None(); +} + + +Option<Error> validateLayout(const string& imagePath) +{ + if (!os::stat::isdir(paths::getImageRootfsPath(imagePath))) { + return Error("No rootfs directory found in image layout"); + } + + if (!os::stat::isfile(paths::getImageManifestPath(imagePath))) { + return Error("No manifest found in image layout"); + } + + return None(); +} + + +Try<AppcImageManifest> parse(const string& value) +{ + Try<JSON::Object> json = JSON::parse<JSON::Object>(value); + if (json.isError()) { + return Error("JSON parse failed: " + json.error()); + } + + Try<AppcImageManifest> manifest = + protobuf::parse<AppcImageManifest>(json.get()); + + if (manifest.isError()) { + return Error("Protobuf parse failed: " + manifest.error()); + } + + Option<Error> error = validateManifest(manifest.get()); + if (error.isSome()) { + return Error("Schema validation failed: " + error.get().message); + } + + return manifest.get(); +} + +} // namespace spec { +} // namespace appc { +} // namespace slave { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/ab75d1d6/src/slave/containerizer/provisioners/appc/spec.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/appc/spec.hpp b/src/slave/containerizer/provisioners/appc/spec.hpp new file mode 100644 index 0000000..5e4308a --- /dev/null +++ b/src/slave/containerizer/provisioners/appc/spec.hpp @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MESOS_APPC_SPEC__ +#define __MESOS_APPC_SPEC__ + +#include <string> + +#include <stout/error.hpp> +#include <stout/option.hpp> + +#include <mesos/mesos.hpp> + +namespace mesos { +namespace internal { +namespace slave { +namespace appc { +namespace spec { + +// Validate if the specified image manifest conforms to the Appc spec. +Option<Error> validateManifest(const AppcImageManifest& manifest); + +// Validate if the specified image ID conforms to the Appc spec. +Option<Error> validateImageID(const std::string& imageId); + +// Validate if the specified image has the disk layout that conforms +// to the Appc spec. +Option<Error> validateLayout(const std::string& imagePath); + +// Parse the AppcImageManifest in the specified JSON string. +Try<AppcImageManifest> parse(const std::string& value); + +} // namespace spec { +} // namespace appc { +} // namespace slave { +} // namespace internal { +} // namespace mesos { + +#endif // __MESOS_APPC_SPEC__ http://git-wip-us.apache.org/repos/asf/mesos/blob/ab75d1d6/src/tests/containerizer/appc_provisioner_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/containerizer/appc_provisioner_tests.cpp b/src/tests/containerizer/appc_provisioner_tests.cpp new file mode 100644 index 0000000..1335cfd --- /dev/null +++ b/src/tests/containerizer/appc_provisioner_tests.cpp @@ -0,0 +1,114 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> + +#include <process/gtest.hpp> + +#include <stout/gtest.hpp> +#include <stout/json.hpp> +#include <stout/os.hpp> +#include <stout/path.hpp> +#include <stout/stringify.hpp> + +#include "slave/containerizer/provisioners/appc/spec.hpp" + +#include "tests/utils.hpp" + +using std::string; +using std::vector; + +using namespace process; + +using namespace mesos::internal::slave::appc; + +namespace mesos { +namespace internal { +namespace tests { + +class AppcProvisionerTest : public TemporaryDirectoryTest {}; + + +TEST_F(AppcProvisionerTest, ValidateImageManifest) +{ + JSON::Value manifest = JSON::parse( + "{" + " \"acKind\": \"ImageManifest\"," + " \"acVersion\": \"0.6.1\"," + " \"name\": \"foo.com/bar\"," + " \"labels\": [" + " {" + " \"name\": \"version\"," + " \"value\": \"1.0.0\"" + " }," + " {" + " \"name\": \"arch\"," + " \"value\": \"amd64\"" + " }," + " {" + " \"name\": \"os\"," + " \"value\": \"linux\"" + " }" + " ]," + " \"annotations\": [" + " {" + " \"name\": \"created\"," + " \"value\": \"1438983392\"" + " }" + " ]" + "}").get(); + + EXPECT_SOME(spec::parse(stringify(manifest))); + + // Incorrect acKind for image manifest. + manifest = JSON::parse( + "{" + " \"acKind\": \"PodManifest\"," + " \"acVersion\": \"0.6.1\"," + " \"name\": \"foo.com/bar\"" + "}").get(); + + EXPECT_ERROR(spec::parse(stringify(manifest))); +} + + +TEST_F(AppcProvisionerTest, ValidateLayout) +{ + string image = os::getcwd(); + + JSON::Value manifest = JSON::parse( + "{" + " \"acKind\": \"ImageManifest\"," + " \"acVersion\": \"0.6.1\"," + " \"name\": \"foo.com/bar\"" + "}").get(); + + ASSERT_SOME(os::write(path::join(image, "manifest"), stringify(manifest))); + + // Missing rootfs. + EXPECT_SOME(spec::validateLayout(image)); + + ASSERT_SOME(os::mkdir(path::join(image, "rootfs", "tmp"))); + ASSERT_SOME(os::write(path::join(image, "rootfs", "tmp", "test"), "test")); + + EXPECT_NONE(spec::validateLayout(image)); +} + +} // namespace tests { +} // namespace internal { +} // namespace mesos {
