Skip to content

Commit d5f4b06

Browse files
authored
Merge pull request #387 from containerd/dev/snapshot
[develop] add create_snapshot() interface
2 parents 58f1508 + ed3355e commit d5f4b06

21 files changed

Lines changed: 1020 additions & 25 deletions

README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ To build overlaybd from source code, the following dependencies are required:
6060
* Libaio, libcurl, libnl3, glib2 and openssl runtime and development libraries.
6161
* CentOS 7/Fedora: `sudo yum install libaio-devel libcurl-devel openssl-devel libnl3-devel libzstd-static e2fsprogs-devel`
6262
* CentOS 8: `sudo yum install libaio-devel libcurl-devel openssl-devel libnl3-devel libzstd-devel e2fsprogs-devel`
63-
* Debian/Ubuntu: `sudo apt install libcurl4-openssl-dev libssl-dev libaio-dev libnl-3-dev libnl-genl-3-dev libgflags-dev libzstd-dev libext2fs-dev`
63+
* Debian/Ubuntu: `sudo apt install libcurl4-openssl-dev libssl-dev libaio-dev libnl-3-dev libnl-genl-3-dev libgflags-dev libzstd-dev libext2fs-dev pkg-config automake libtool # libgtest-dev // for test`
6464
* Mariner/AzureLinux: `sudo yum install libaio-devel libcurl-devel openssl-devel libnl3-devel e2fsprogs-devel glibc-devel libzstd-devel binutils ca-certificates-microsoft build-essential`
6565

6666
#### Build
@@ -169,7 +169,11 @@ Default configure file `overlaybd.json` is installed to `/etc/overlaybd/`.
169169
"updateInterval": 60000000
170170
},
171171
"enableAudit": true,
172-
"auditPath": "/var/log/overlaybd-audit.log"
172+
"auditPath": "/var/log/overlaybd-audit.log",
173+
"serviceConfig": {
174+
"enable": false,
175+
"address": "http://127.0.0.1:9862"
176+
}
173177
}
174178
```
175179

@@ -210,6 +214,8 @@ Default configure file `overlaybd.json` is installed to `/etc/overlaybd/`.
210214
| certConfig.certFile | The path for SSL/TLS client certificate file |
211215
| certConfig.keyFile | The path for SSL/TLS client key file |
212216
| userAgent | customized userAgent to identify HTTP request. default value is package version like 'overlaybd/1.1.14-6c449832' |
217+
| serviceConfig.enable | Enable live snapshot API service, `false` is default. |
218+
| serviceConfig.address | API service listening address, default `http://127.0.0.1:9862`. |
213219

214220

215221
> NOTE: `download` is the config for background downloading. After an overlaybd device is lauched, a background task will be running to fetch the whole blobs into local directories. After downloading, I/O requests are directed to local files. Unlike other options, download config is reloaded when a device launching.
@@ -381,6 +387,68 @@ At last, compression may be needed.
381387
```
382388
The zfile can be used as lower layer with online decompression.
383389

390+
### Live Snapshot
391+
392+
Overlaybd supports creating live snapshots without stopping the device. This feature allows you to capture the current state of a writable layer and stack a new writable layer on top.
393+
394+
#### Device ID
395+
396+
To use the live snapshot feature, you need to specify a device ID when creating the overlaybd device. The device ID is appended to the config path with a semicolon separator:
397+
398+
```bash
399+
echo -n dev_config=overlaybd//root/config.v1.json;123 > /sys/kernel/config/target/core/user_1/vol1/control
400+
```
401+
402+
#### Enable API Service
403+
404+
Add the following to your `overlaybd.json`:
405+
406+
```json
407+
"serviceConfig": {
408+
"enable": true,
409+
"address": "http://127.0.0.1:9862"
410+
}
411+
```
412+
413+
#### Create Snapshot
414+
415+
Send an HTTP POST request to the `/snapshot` endpoint:
416+
417+
```bash
418+
curl -X POST "http://127.0.0.1:9862/snapshot?dev_id=123&config=/path/to/new_config.json"
419+
```
420+
421+
The response will be in JSON format:
422+
423+
```json
424+
{
425+
"success": true,
426+
"message": "Snapshot created successfully"
427+
}
428+
```
429+
430+
#### New Config Format
431+
432+
The new config file should include the current upper layer as the last lower layer:
433+
434+
```json
435+
{
436+
"lowers": [
437+
{
438+
"file": "/opt/overlaybd/layer0"
439+
},
440+
{
441+
"file": "/path/to/current_upper_data.lsmt"
442+
}
443+
],
444+
"upper": {
445+
"index": "/path/to/new_upper_index.lsmt",
446+
"data": "/path/to/new_upper_data.lsmt"
447+
}
448+
}
449+
```
450+
451+
**Note**: The new upper layer must be different from the old upper layer.
384452

385453
## Kernel module
386454

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_library(overlaybd_image_lib
1515
prefetch.cpp
1616
tools/sha256file.cpp
1717
tools/comm_func.cpp
18+
api_server.cpp
1819
)
1920
target_include_directories(overlaybd_image_lib PUBLIC
2021
${CURL_INCLUDE_DIRS}

src/api_server.cpp

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
Copyright The Overlaybd Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include <photon/net/http/server.h>
18+
#include <photon/net/socket.h>
19+
#include <photon/net/http/url.h>
20+
#include <map>
21+
#include <string>
22+
#include <string_view>
23+
#include "image_service.h"
24+
#include "image_file.h"
25+
#include "api_server.h"
26+
27+
class ApiHandler : public photon::net::http::HTTPHandler {
28+
public:
29+
ImageService *imgservice;
30+
std::map<std::string, std::string> params;
31+
32+
ApiHandler(ImageService *imgservice) : imgservice(imgservice) {}
33+
int handle_request(photon::net::http::Request& req,
34+
photon::net::http::Response& resp,
35+
std::string_view) override {
36+
auto target = req.target(); // string view, format: /snapshot?dev_id=${devID}&config=${config}
37+
std::string_view query("");
38+
auto pos = target.find('?');
39+
if (pos != std::string_view::npos) {
40+
query = target.substr(pos + 1);
41+
}
42+
// auto query = req.query();
43+
LOG_INFO("Snapshot query: `", query); // string view, format: dev_id=${devID}&config=${config}
44+
parse_params(query);
45+
auto dev_id = params["dev_id"];
46+
auto config_path = params["config"];
47+
LOG_DEBUG("dev_id: `, config: `", dev_id, config_path);
48+
49+
int code;
50+
std::string msg;
51+
ImageFile* img_file = nullptr;
52+
53+
if (dev_id.empty() || config_path.empty()) {
54+
code = 400;
55+
msg = std::string(R"delimiter({
56+
"success": false,
57+
"message": "Missing dev_id or config in snapshot request"
58+
})delimiter");
59+
goto EXIT;
60+
}
61+
62+
img_file = imgservice->find_image_file(dev_id);
63+
if (!img_file) {
64+
code = 404;
65+
msg = std::string(R"delimiter({
66+
"success": false,
67+
"message": "Image file not found"
68+
})delimiter");
69+
goto EXIT;
70+
}
71+
72+
if (img_file->create_snapshot(config_path.c_str()) < 0) {
73+
code = 500;
74+
msg = std::string(R"delimiter({
75+
"success": false,
76+
"message": "Failed to create snapshot`"
77+
})delimiter");
78+
goto EXIT;
79+
}
80+
81+
code = 200;
82+
msg = std::string(R"delimiter({
83+
"success": true,
84+
"message": "Snapshot created successfully"
85+
})delimiter");
86+
87+
EXIT:
88+
resp.set_result(code);
89+
resp.headers.content_length(msg.size());
90+
resp.keep_alive(true);
91+
auto ret_w = resp.write((void*)msg.c_str(), msg.size());
92+
if (ret_w != (ssize_t)msg.size()) {
93+
LOG_ERRNO_RETURN(0, -1, "send body failed, target: `, `", req.target(), VALUE(ret_w));
94+
}
95+
LOG_DEBUG("send body done");
96+
return 0;
97+
}
98+
99+
void parse_params(std::string_view query) { // format: dev_id=${devID}&config=${config}...
100+
if (query.empty())
101+
return;
102+
103+
size_t start = 0;
104+
while (start < query.length()) {
105+
auto end = query.find('&', start);
106+
if (end == std::string_view::npos) { // last one
107+
end = query.length();
108+
}
109+
110+
auto param = query.substr(start, end - start);
111+
auto eq_pos = param.find('=');
112+
if (eq_pos != std::string_view::npos) {
113+
auto key = param.substr(0, eq_pos);
114+
auto value = param.substr(eq_pos + 1);
115+
116+
// url decode
117+
auto decoded_key = photon::net::http::url_unescape(key);
118+
auto decoded_value = photon::net::http::url_unescape(value);
119+
params[decoded_key] = decoded_value;
120+
} else {
121+
// key without value
122+
auto key = photon::net::http::url_unescape(param);
123+
params[key] = "";
124+
}
125+
start = end + 1;
126+
}
127+
}
128+
};
129+
130+
struct ApiServer {
131+
photon::net::ISocketServer* tcpserver = nullptr;
132+
photon::net::http::HTTPServer* httpserver = nullptr;
133+
ApiHandler* handler = nullptr;
134+
135+
ApiServer(ImageService *imgservice) : handler(new ApiHandler(imgservice)) {}
136+
137+
~ApiServer() {
138+
delete handler;
139+
if(tcpserver) {
140+
safe_delete(tcpserver);
141+
}
142+
if(httpserver) {
143+
safe_delete(httpserver);
144+
}
145+
}
146+
147+
int init(const std::string &addr) {
148+
photon::net::http::URL url(addr);
149+
std::string host = url.host().data(); // the string pointed by data() doesn't end up with '\0'
150+
auto pos = host.find(":");
151+
if (pos != host.npos) {
152+
host.resize(pos);
153+
}
154+
tcpserver = photon::net::new_tcp_socket_server();
155+
tcpserver->setsockopt(SOL_SOCKET, SO_REUSEPORT, 1);
156+
if(tcpserver->bind(url.port(), photon::net::IPAddr(host.c_str())) < 0)
157+
LOG_ERRNO_RETURN(0, -1, "Failed to bind api server port `", url.port());
158+
if(tcpserver->listen() < 0)
159+
LOG_ERRNO_RETURN(0, -1, "Failed to listen api server port `", url.port());
160+
httpserver = photon::net::http::new_http_server();
161+
httpserver->add_handler(handler, false, "/snapshot");
162+
tcpserver->set_handler(httpserver->get_connection_handler());
163+
tcpserver->start_loop();
164+
LOG_DEBUG("Api server listening on `:`, path: `", host, url.port(), "/snapshot");
165+
return 0;
166+
}
167+
};
168+
169+
int start_api_server(ApiServer *&api_server , ImageService *imgservice, const std::string &addr) {
170+
api_server = new ApiServer(imgservice);
171+
return api_server->init(addr);
172+
}
173+
174+
int stop_api_server(ApiServer *api_server) {
175+
safe_delete(api_server);
176+
}

src/api_server.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright The Overlaybd Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
int start_api_server(ApiServer *&api_server , ImageService *imgservice, const std::string &addr);
20+
int stop_api_server(ApiServer *api_server);

src/config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ struct CertConfig : public ConfigUtils::Config {
131131
APPCFG_PARA(keyFile, std::string, "");
132132
};
133133

134+
struct ServiceConfig : public ConfigUtils::Config {
135+
APPCFG_CLASS
136+
APPCFG_PARA(enable, bool, false);
137+
APPCFG_PARA(address, std::string, "http://127.0.0.1:9862");
138+
};
139+
134140
struct GlobalConfig : public ConfigUtils::Config {
135141
APPCFG_CLASS
136142

@@ -155,6 +161,7 @@ struct GlobalConfig : public ConfigUtils::Config {
155161
APPCFG_PARA(prefetchConfig, PrefetchConfig);
156162
APPCFG_PARA(certConfig, CertConfig);
157163
APPCFG_PARA(userAgent, std::string, OVERLAYBD_VERSION);
164+
APPCFG_PARA(serviceConfig, ServiceConfig);
158165
};
159166

160167
struct AuthConfig : public ConfigUtils::Config {
@@ -170,4 +177,5 @@ struct ImageAuthResponse : public ConfigUtils::Config {
170177
APPCFG_PARA(data, AuthConfig);
171178
};
172179

180+
173181
} // namespace ImageConfigNS

src/example_config/overlaybd.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,9 @@
3636
},
3737
"enableAudit": true,
3838
"auditPath": "/var/log/overlaybd-audit.log",
39-
"registryFsVersion": "v2"
39+
"registryFsVersion": "v2",
40+
"serviceConfig": {
41+
"enable": false,
42+
"address": "http://127.0.0.1:9862"
43+
}
4044
}

0 commit comments

Comments
 (0)