Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(overlaybd_image_lib
prefetch.cpp
tools/sha256file.cpp
tools/comm_func.cpp
api_server.cpp
)
target_include_directories(overlaybd_image_lib PUBLIC
${CURL_INCLUDE_DIRS}
Expand Down
142 changes: 142 additions & 0 deletions src/api_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
Copyright The Overlaybd Authors

Licensed 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 <photon/net/http/url.h>
#include <string_view>
#include "image_service.h"
#include "image_file.h"
#include "api_server.h"

int ApiHandler::handle_request(photon::net::http::Request& req,
photon::net::http::Response& resp,
std::string_view) {
auto target = req.target(); // string view, format: /snapshot?dev_id=${devID}&config=${config}
std::string_view query("");
auto pos = target.find('?');
if (pos != std::string_view::npos) {
query = target.substr(pos + 1);
}
LOG_DEBUG("Snapshot query: `", query); // string view, format: dev_id=${devID}&config=${config}
parse_params(query);
auto dev_id = params["dev_id"];
auto config_path = params["config"];
LOG_DEBUG("dev_id: `, config: `", dev_id, config_path);

int code;
std::string msg;
ImageFile* img_file = nullptr;

if (dev_id.empty() || config_path.empty()) {
code = 400;
msg = std::string(R"delimiter({
"success": false,
"message": "Missing dev_id or config in snapshot request"
})delimiter");
goto EXIT;
}

img_file = imgservice->find_image_file(dev_id);
if (!img_file) {
code = 404;
msg = std::string(R"delimiter({
"success": false,
"message": "Image file not found"
})delimiter");
goto EXIT;
}

if (img_file->create_snapshot(config_path.c_str()) < 0) {
code = 500;
msg = std::string(R"delimiter({
"success": false,
"message": "Failed to create snapshot`"
})delimiter");
goto EXIT;
}

code = 200;
msg = std::string(R"delimiter({
"success": true,
"message": "Snapshot created successfully"
})delimiter");

EXIT:
resp.set_result(code);
resp.headers.content_length(msg.size());
resp.keep_alive(true);
auto ret_w = resp.write((void*)msg.c_str(), msg.size());
if (ret_w != (ssize_t)msg.size()) {
LOG_ERRNO_RETURN(0, -1, "send body failed, target: `, `", req.target(), VALUE(ret_w));
}
LOG_DEBUG("send body done");
return 0;
}

void ApiHandler::parse_params(std::string_view query) { // format: dev_id=${devID}&config=${config}...
if (query.empty())
return;

size_t start = 0;
while (start < query.length()) {
auto end = query.find('&', start);
if (end == std::string_view::npos) { // last one
end = query.length();
}

auto param = query.substr(start, end - start);
auto eq_pos = param.find('=');
if (eq_pos != std::string_view::npos) {
auto key = param.substr(0, eq_pos);
auto value = param.substr(eq_pos + 1);

// url decode
auto decoded_key = photon::net::http::url_unescape(key);
auto decoded_value = photon::net::http::url_unescape(value);
params[decoded_key] = decoded_value;
} else {
// key without value
auto key = photon::net::http::url_unescape(param);
params[key] = "";
}
start = end + 1;
}
}

ApiServer::ApiServer(const std::string &addr, ApiHandler* handler) {
photon::net::http::URL url(addr);
std::string host = url.host().data(); // the string pointed by data() doesn't end up with '\0'
auto pos = host.find(":");
if (pos != host.npos) {
host.resize(pos);
}
tcpserver = photon::net::new_tcp_socket_server();
tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1);
if(tcpserver->bind(url.port(), photon::net::IPAddr(host.c_str())) < 0)
LOG_ERRNO_RETURN(0, , "Failed to bind api server port `", url.port());
if(tcpserver->listen() < 0)
LOG_ERRNO_RETURN(0, , "Failed to listen api server port `", url.port());
httpserver = photon::net::http::new_http_server();
httpserver->add_handler(handler, false, "/snapshot");
tcpserver->set_handler(httpserver->get_connection_handler());
tcpserver->start_loop();
ready = true;
LOG_DEBUG("Api server listening on `:`, path: `", host, url.port(), "/snapshot");
}

ApiServer::~ApiServer() {
delete tcpserver;
delete httpserver;
}
45 changes: 45 additions & 0 deletions src/api_server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright The Overlaybd Authors

Licensed 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.
*/

#pragma once

#include <map>
#include <string>
#include <photon/net/http/server.h>
#include <photon/net/socket.h>

class ImageService;

class ApiHandler : public photon::net::http::HTTPHandler {
public:
ImageService *imgservice;
std::map<std::string, std::string> params;

ApiHandler(ImageService *imgservice) : imgservice(imgservice) {}
int handle_request(photon::net::http::Request& req,
photon::net::http::Response& resp,
std::string_view) override;
void parse_params(std::string_view query);
};

struct ApiServer {
photon::net::ISocketServer* tcpserver = nullptr;
photon::net::http::HTTPServer* httpserver = nullptr;
bool ready = false;

ApiServer(const std::string &addr, ApiHandler* handler);
~ApiServer();
};
3 changes: 1 addition & 2 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,9 @@ struct CertConfig : public ConfigUtils::Config {
struct ServiceConfig : public ConfigUtils::Config {
APPCFG_CLASS
APPCFG_PARA(enable, bool, false);
APPCFG_PARA(domainSocket, std::string, "");
APPCFG_PARA(address, std::string, "http://127.0.0.1:9862");
};


struct GlobalConfig : public ConfigUtils::Config {
APPCFG_CLASS

Expand Down
2 changes: 1 addition & 1 deletion src/example_config/overlaybd.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@
"registryFsVersion": "v2",
"serviceConfig": {
"enable": false,
"domainSocket": "/var/run/overlaybd.sock"
"address": "http://127.0.0.1:9862"
}
}
1 change: 1 addition & 0 deletions src/image_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class ImageFile : public photon::fs::ForwardFile {
// load new config file to get the snapshot layer path
// open new upper layer
// restack() current RW layer as snapshot layer
LOG_INFO("call create_snapshot, dev_id: `", m_dev_id);
return 0;
}

Expand Down
21 changes: 14 additions & 7 deletions src/image_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "image_service.h"
#include "config.h"
#include "image_file.h"
#include "api_server.h"
#include <photon/common/alog.h>
#include <photon/common/alog-stdstring.h>
#include <photon/common/io-alloc.h>
Expand Down Expand Up @@ -441,18 +442,22 @@ int ImageService::init() {
}
}
if (global_conf.serviceConfig().enable()) {
auto sock_path = global_conf.serviceConfig().domainSocket();
if (access(sock_path.c_str(), 0) == 0) {
if (unlink(sock_path.c_str()) != 0) {
LOG_ERRNO_RETURN(0, -1, "failed to remove old socket file");
}
}
// auto sock_path = global_conf.serviceConfig().domainSocket();
// if (access(sock_path.c_str(), 0) == 0) {
// if (unlink(sock_path.c_str()) != 0) {
// LOG_ERRNO_RETURN(0, -1, "failed to remove old socket file");
// }
// }
// listen the domainSocket and create a HTTP SERVER
/*
handler definition:
- create a live snapshot for a imageFile
/snapshot?dev_id=${devID}&config=${config}
*/
api_handler.reset(new ApiHandler(this));
api_server = new ApiServer(global_conf.serviceConfig().address(), api_handler.get());
if(!api_server->ready)
LOG_ERROR_RETURN(0, -1, "Failed to start http server for live snapshot");
}
return 0;
}
Expand Down Expand Up @@ -540,6 +545,8 @@ ImageService::~ImageService() {
delete global_fs.srcfs;
delete global_fs.io_alloc;
delete exporter;
delete api_server;

LOG_INFO("image service is fully stopped");
}

Expand All @@ -550,4 +557,4 @@ ImageService *create_image_service(const char *config_path) {
return nullptr;
}
return ret;
}
}
4 changes: 4 additions & 0 deletions src/image_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ struct ImageAuthResponse : public ConfigUtils::Config {
};

struct ImageFile;
class ApiHandler;
struct ApiServer;

class ImageService {
public:
Expand All @@ -67,6 +69,8 @@ class ImageService {
struct GlobalFs global_fs;
std::unique_ptr<OverlayBDMetric> metrics;
ExporterServer *exporter = nullptr;
std::unique_ptr<ApiHandler> api_handler;
ApiServer *api_server = nullptr;

private:
int read_global_config_and_set();
Expand Down
2 changes: 1 addition & 1 deletion src/overlaybd/tar/erofs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ include(FetchContent)

FetchContent_Declare(
erofs-utils
GIT_REPOSITORY https://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs-utils.git
GIT_REPOSITORY https://github.com/erofs/erofs-utils.git
GIT_TAG eec6f7a2755dfccc8f655aa37cf6f26db9164e60
)

Expand Down
Loading