1359 lines
37 KiB
C
1359 lines
37 KiB
C
/******************************************************************************
|
|
* Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved.
|
|
* iSulad licensed under the Mulan PSL v1.
|
|
* You can use this software according to the terms and conditions of the Mulan PSL v1.
|
|
* You may obtain a copy of Mulan PSL v1 at:
|
|
* http://license.coscl.org.cn/MulanPSL
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
|
|
* PURPOSE.
|
|
* See the Mulan PSL v1 for more details.
|
|
* Author: tanyifeng
|
|
* Create: 2017-11-22
|
|
* Description: provide container extend callback function definition
|
|
*********************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#include <lcr/lcrcontainer.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
#include <pthread.h>
|
|
#include <sys/sysinfo.h>
|
|
|
|
#include "log.h"
|
|
#include "engine.h"
|
|
#include "collector.h"
|
|
#include "console.h"
|
|
#include "lcrd_config.h"
|
|
#include "config.h"
|
|
#include "restartmanager.h"
|
|
#include "image.h"
|
|
#include "securec.h"
|
|
#include "verify.h"
|
|
#include "container_inspect.h"
|
|
#include "containers_store.h"
|
|
#include "containers_gc.h"
|
|
#include "execution_extend.h"
|
|
#include "sysinfo.h"
|
|
#include "health_check.h"
|
|
|
|
#ifdef ENABLE_OCI_IMAGE
|
|
#include "oci_rootfs_export.h"
|
|
#endif
|
|
|
|
#include "filters.h"
|
|
#include "utils.h"
|
|
#include "error.h"
|
|
|
|
struct stats_context {
|
|
struct filters_args *stats_filters;
|
|
container_stats_request *stats_config;
|
|
};
|
|
|
|
static int service_events_handler(const struct lcrd_events_request *request,
|
|
const stream_func_wrapper *stream)
|
|
{
|
|
int ret = 0;
|
|
char *name = NULL;
|
|
container_t *container = NULL;
|
|
|
|
name = request->id;
|
|
|
|
/* check whether specified container exists */
|
|
if (name != NULL) {
|
|
container = containers_store_get(name);
|
|
if (container == NULL) {
|
|
ERROR("No such container:%s", name);
|
|
lcrd_set_error_message("No such container:%s", name);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
container_unref(container);
|
|
}
|
|
|
|
ret = events_subscribe(name, &request->since, &request->until, stream);
|
|
if (ret < 0) {
|
|
ERROR("Failed to subscribe events buffer");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (add_monitor_client(name, &request->since, &request->until, stream)) {
|
|
ERROR("Failed to add events monitor client");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int container_events_cb(const struct lcrd_events_request *request, const stream_func_wrapper *stream)
|
|
{
|
|
int ret = 0;
|
|
uint32_t cc = LCRD_SUCCESS;
|
|
|
|
DAEMON_CLEAR_ERRMSG();
|
|
|
|
if (request == NULL) {
|
|
ERROR("Invalid NULL input");
|
|
return -1;
|
|
}
|
|
|
|
if (stream == NULL) {
|
|
ERROR("Should provide stream function in events");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto out;
|
|
}
|
|
|
|
ret = service_events_handler(request, stream);
|
|
if (ret != 0) {
|
|
ERROR("Failed to add events monitor");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return (cc == LCRD_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
static int dup_container_stats_request(const container_stats_request *src, container_stats_request **dest)
|
|
{
|
|
int ret = -1;
|
|
char *json = NULL;
|
|
parser_error err = NULL;
|
|
|
|
if (src == NULL) {
|
|
*dest = NULL;
|
|
return 0;
|
|
}
|
|
|
|
json = container_stats_request_generate_json(src, NULL, &err);
|
|
if (json == NULL) {
|
|
ERROR("Failed to generate json: %s", err);
|
|
goto out;
|
|
}
|
|
*dest = container_stats_request_parse_data(json, NULL, &err);
|
|
if (*dest == NULL) {
|
|
ERROR("Failed to parse json: %s", err);
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
|
|
out:
|
|
free(err);
|
|
free(json);
|
|
return ret;
|
|
}
|
|
|
|
static void free_stats_context(struct stats_context *ctx)
|
|
{
|
|
if (ctx == NULL) {
|
|
return;
|
|
}
|
|
filters_args_free(ctx->stats_filters);
|
|
ctx->stats_filters = NULL;
|
|
free_container_stats_request(ctx->stats_config);
|
|
ctx->stats_config = NULL;
|
|
free(ctx);
|
|
}
|
|
static struct stats_context *stats_context_new(const container_stats_request *request)
|
|
{
|
|
struct stats_context *ctx = NULL;
|
|
|
|
ctx = util_common_calloc_s(sizeof(struct stats_context));
|
|
if (ctx == NULL) {
|
|
ERROR("Out of memory");
|
|
return NULL;
|
|
}
|
|
ctx->stats_filters = filters_args_new();
|
|
if (ctx->stats_filters == NULL) {
|
|
ERROR("Out of memory");
|
|
goto cleanup;
|
|
}
|
|
if (dup_container_stats_request(request, &(ctx->stats_config)) != 0) {
|
|
ERROR("Failed to dup stats request");
|
|
goto cleanup;
|
|
}
|
|
|
|
return ctx;
|
|
cleanup:
|
|
free_stats_context(ctx);
|
|
return NULL;
|
|
}
|
|
static const char *accepted_stats_filter_tags[] = {
|
|
"id",
|
|
"label",
|
|
"name",
|
|
NULL
|
|
};
|
|
|
|
static int copy_map_labels(const container_config *config, map_t **map_labels)
|
|
{
|
|
*map_labels = map_new(MAP_STR_STR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC);
|
|
if (*map_labels == NULL) {
|
|
ERROR("Out of memory");
|
|
return -1;
|
|
}
|
|
|
|
if (config != NULL && config->labels != NULL && config->labels->len != 0) {
|
|
size_t i;
|
|
json_map_string_string *labels = config->labels;
|
|
|
|
for (i = 0; i < labels->len; i++) {
|
|
// Copy labels to internal map for filters
|
|
if (!map_replace(*map_labels, (void *)labels->keys[i], labels->values[i])) {
|
|
ERROR("Failed to insert labels to map");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static container_info *get_container_stats(const container_t *cont, const struct engine_container_info *einfo,
|
|
const struct stats_context *ctx)
|
|
{
|
|
int ret = 0;
|
|
uint64_t sysmem_limit;
|
|
uint64_t sys_cpu_usage = 0;
|
|
container_info *info = NULL;
|
|
map_t *map_labels = NULL;
|
|
|
|
info = util_common_calloc_s(sizeof(container_info));
|
|
if (info == NULL) {
|
|
ERROR("Out of memory");
|
|
return NULL;
|
|
}
|
|
|
|
info->id = util_strdup_s(cont->common_config->id);
|
|
info->has_pid = einfo->has_pid;
|
|
info->pid = (int32_t)einfo->pid;
|
|
info->status = (int)einfo->status;
|
|
info->pids_current = einfo->pids_current;
|
|
info->cpu_use_nanos = einfo->cpu_use_nanos;
|
|
info->blkio_read = einfo->blkio_read;
|
|
info->blkio_write = einfo->blkio_write;
|
|
info->mem_used = einfo->mem_used;
|
|
info->mem_limit = einfo->mem_limit;
|
|
info->kmem_used = einfo->kmem_used;
|
|
info->kmem_limit = einfo->kmem_limit;
|
|
|
|
sysmem_limit = get_default_total_mem_size();
|
|
if (get_system_cpu_usage(&sys_cpu_usage)) {
|
|
WARN("Failed to get system cpu usage");
|
|
}
|
|
|
|
if (sysmem_limit > 0) {
|
|
if (info->mem_limit > sysmem_limit) {
|
|
info->mem_limit = sysmem_limit;
|
|
}
|
|
if (info->kmem_limit > sysmem_limit) {
|
|
info->kmem_limit = sysmem_limit;
|
|
}
|
|
}
|
|
info->cpu_system_use = sys_cpu_usage;
|
|
info->online_cpus = (uint32_t)get_nprocs();
|
|
|
|
info->image_type = util_strdup_s(cont->common_config->image_type);
|
|
|
|
if (copy_map_labels(cont->common_config->config, &map_labels) != 0) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!filters_args_match(ctx->stats_filters, "id", info->id)) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Do not include container if any of the labels don't match
|
|
if (!filters_args_match_kv_list(ctx->stats_filters, "label", map_labels)) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
map_free(map_labels);
|
|
if (ret != 0) {
|
|
free_container_info(info);
|
|
info = NULL;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
static struct stats_context *fold_stats_filter(const container_stats_request *request)
|
|
{
|
|
size_t i, j;
|
|
struct stats_context *ctx = NULL;
|
|
|
|
ctx = stats_context_new(request);
|
|
if (ctx == NULL) {
|
|
ERROR("Out of memory");
|
|
return NULL;
|
|
}
|
|
|
|
if (request->containers != NULL && request->containers_len > 0) {
|
|
ctx->stats_config->all = true;
|
|
}
|
|
|
|
if (request->filters == NULL) {
|
|
return ctx;
|
|
}
|
|
|
|
for (i = 0; i < request->filters->len; i++) {
|
|
if (!filters_args_valid_key(accepted_stats_filter_tags, sizeof(accepted_stats_filter_tags) / sizeof(char *),
|
|
request->filters->keys[i])) {
|
|
ERROR("Invalid filter '%s'", request->filters->keys[i]);
|
|
lcrd_set_error_message("Invalid filter '%s'", request->filters->keys[i]);
|
|
goto error_out;
|
|
}
|
|
for (j = 0; j < request->filters->values[i]->len; j++) {
|
|
bool bret = false;
|
|
bret = filters_args_add(ctx->stats_filters, request->filters->keys[i],
|
|
request->filters->values[i]->keys[j]);
|
|
if (!bret) {
|
|
ERROR("Add filter args failed");
|
|
goto error_out;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ctx;
|
|
error_out:
|
|
free_stats_context(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
static int service_stats_make_memory(container_info ***stats_arr, size_t num)
|
|
{
|
|
if (num > SIZE_MAX / sizeof(container_info *)) {
|
|
return -1;
|
|
}
|
|
|
|
*stats_arr = util_common_calloc_s(num * sizeof(container_info *));
|
|
if (*stats_arr == NULL) {
|
|
ERROR("Out of memory");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pack_stats_response(container_stats_response *response, uint32_t cc, size_t info_len,
|
|
container_info **info)
|
|
{
|
|
if (response == NULL) {
|
|
return;
|
|
}
|
|
response->container_stats_len = info_len;
|
|
response->container_stats = info;
|
|
response->cc = cc;
|
|
if (g_lcrd_errmsg != NULL) {
|
|
response->errmsg = util_strdup_s(g_lcrd_errmsg);
|
|
DAEMON_CLEAR_ERRMSG();
|
|
}
|
|
}
|
|
|
|
static int stats_get_all_containers_id(const container_stats_request *request, char ***idsarray, size_t *ids_len,
|
|
bool *check_exists)
|
|
{
|
|
int ret = -1;
|
|
char **array = NULL;
|
|
|
|
if (request == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (request->containers_len > 0 && request->containers != NULL) {
|
|
size_t n;
|
|
for (n = 0; n < request->containers_len; n++) {
|
|
if (!util_valid_container_id_or_name(request->containers[n])) {
|
|
ERROR("Invalid container name: %s", request->containers[n]);
|
|
lcrd_set_error_message("Invalid container name: %s", request->containers[n]);
|
|
goto cleanup;
|
|
}
|
|
if (util_array_append(&array, request->containers[n]) != 0) {
|
|
ERROR("Can not append array");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
*check_exists = true;
|
|
} else {
|
|
array = containers_store_list_ids();
|
|
}
|
|
*ids_len = util_array_len((const char **)array);
|
|
*idsarray = array;
|
|
array = NULL;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
util_free_array(array);
|
|
return ret;
|
|
}
|
|
|
|
static int get_containers_stats(const char *runtime, char **idsarray, size_t ids_len, const struct stats_context *ctx,
|
|
bool check_exists, container_info ***info, size_t *info_len)
|
|
{
|
|
int ret = 0;
|
|
int nret;
|
|
size_t i;
|
|
char *engine_path = NULL;
|
|
struct engine_operation *engine_ops = NULL;
|
|
|
|
engine_ops = engines_get_handler(runtime);
|
|
if (engine_ops == NULL || engine_ops->engine_get_container_status_op == NULL ||
|
|
engine_ops->engine_free_container_status_op == NULL) {
|
|
ERROR("Failed to get engine stats operations");
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
engine_path = conf_get_routine_rootdir(runtime);
|
|
if (engine_path == NULL) {
|
|
ERROR("Get engine path failed");
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
nret = service_stats_make_memory(info, ids_len);
|
|
if (nret != 0) {
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < ids_len; i++) {
|
|
struct engine_container_info einfo = { 0 };
|
|
container_t *cont = NULL;
|
|
|
|
cont = containers_store_get(idsarray[i]);
|
|
if (cont == NULL) {
|
|
if (check_exists) {
|
|
ERROR("No such container: %s", idsarray[i]);
|
|
lcrd_set_error_message("No such container: %s", idsarray[i]);
|
|
ret = -1;
|
|
goto cleanup;
|
|
}
|
|
continue;
|
|
}
|
|
if (is_running(cont->state)) {
|
|
nret = engine_ops->engine_get_container_status_op(cont->common_config->id, engine_path, &einfo);
|
|
if (nret != 0) {
|
|
engine_ops->engine_clear_errmsg_op();
|
|
container_unref(cont);
|
|
continue;
|
|
}
|
|
} else {
|
|
if (!ctx->stats_config->all) {
|
|
container_unref(cont);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
(*info)[*info_len] = get_container_stats(cont, &einfo, ctx);
|
|
container_unref(cont);
|
|
engine_ops->engine_free_container_status_op(&einfo);
|
|
if ((*info)[*info_len] == NULL) {
|
|
continue;
|
|
}
|
|
(*info_len)++;
|
|
}
|
|
cleanup:
|
|
free(engine_path);
|
|
return ret;
|
|
}
|
|
|
|
static int container_stats_cb(const container_stats_request *request,
|
|
container_stats_response **response)
|
|
{
|
|
bool check_exists = false;
|
|
size_t ids_len = 0;
|
|
size_t info_len = 0;
|
|
uint32_t cc = LCRD_SUCCESS;
|
|
char **idsarray = NULL;
|
|
container_info **info = NULL;
|
|
struct stats_context *ctx = NULL;
|
|
|
|
DAEMON_CLEAR_ERRMSG();
|
|
if (request == NULL || response == NULL) {
|
|
ERROR("Invalid NULL input");
|
|
return -1;
|
|
}
|
|
|
|
*response = util_common_calloc_s(sizeof(container_stats_response));
|
|
if (*response == NULL) {
|
|
ERROR("Out of memory");
|
|
cc = LCRD_ERR_MEMOUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
if (request->runtime == NULL) {
|
|
ERROR("Receive NULL Request runtime");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
ctx = fold_stats_filter(request);
|
|
if (ctx == NULL) {
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
if (stats_get_all_containers_id(request, &idsarray, &ids_len, &check_exists) != 0) {
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
if (ids_len == 0) {
|
|
goto pack_response;
|
|
}
|
|
|
|
if (get_containers_stats(request->runtime, idsarray, ids_len, ctx, check_exists, &info, &info_len)) {
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
pack_response:
|
|
pack_stats_response(*response, cc, info_len, info);
|
|
util_free_array(idsarray);
|
|
free_stats_context(ctx);
|
|
return (cc == LCRD_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
static int runtime_resume(const char *id, const char *runtime, const char *rootpath)
|
|
{
|
|
int ret = 0;
|
|
struct engine_operation *engine_ops = NULL;
|
|
|
|
engine_ops = engines_get_handler(runtime);
|
|
if (engine_ops == NULL || engine_ops->engine_resume_op == NULL) {
|
|
DEBUG("Failed to get engine resume operations");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!engine_ops->engine_resume_op(id, rootpath)) {
|
|
DEBUG("Resume container %s failed", id);
|
|
const char *tmpmsg = NULL;
|
|
tmpmsg = engine_ops->engine_get_errmsg_op();
|
|
lcrd_set_error_message("Resume container error;%s",
|
|
(tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg
|
|
: DEF_ERR_RUNTIME_STR);
|
|
|
|
engine_ops->engine_clear_errmsg_op();
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int resume_container(container_t *cont)
|
|
{
|
|
int ret = 0;
|
|
const char *id = cont->common_config->id;
|
|
|
|
container_lock(cont);
|
|
|
|
if (!is_running(cont->state)) {
|
|
ERROR("Container %s is not running", id);
|
|
lcrd_set_error_message("Container %s is not running", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!is_paused(cont->state)) {
|
|
ERROR("Container %s is not paused", id);
|
|
lcrd_set_error_message("Container %s is not paused", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (runtime_resume(id, cont->runtime, cont->root_path)) {
|
|
ERROR("Failed to resume container:%s", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
state_reset_paused(cont->state);
|
|
|
|
if (container_to_disk(cont)) {
|
|
ERROR("Failed to save container \"%s\" to disk", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
container_unlock(cont);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef ENABLE_OCI_IMAGE
|
|
static int oci_image_export_rootfs(const char *id, const char *file)
|
|
{
|
|
int ret = 0;
|
|
rootfs_export_request *request = NULL;
|
|
rootfs_export_response *response = NULL;
|
|
|
|
if (id == NULL || file == NULL) {
|
|
ERROR("Invalid input arguments");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
request = util_common_calloc_s(sizeof(rootfs_export_request));
|
|
if (request == NULL) {
|
|
ERROR("Memory out");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
request->id = util_strdup_s(id);
|
|
request->file = util_strdup_s(file);
|
|
|
|
ret = export_rootfs(request, &response);
|
|
if (ret != 0) {
|
|
ERROR("Failed to export rootfs to %s from container %s", file, id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
free_rootfs_export_request(request);
|
|
free_rootfs_export_response(response);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int export_container(container_t *cont, const char *file)
|
|
{
|
|
int ret = 0;
|
|
|
|
container_lock(cont);
|
|
|
|
#ifdef ENABLE_OCI_IMAGE
|
|
if (oci_image_export_rootfs(cont->common_config->id, file)) {
|
|
ret = -1;
|
|
}
|
|
#endif
|
|
|
|
container_unlock(cont);
|
|
return ret;
|
|
}
|
|
|
|
static void pack_resume_response(container_resume_response *response, uint32_t cc, const char *id)
|
|
{
|
|
if (response == NULL) {
|
|
return;
|
|
}
|
|
response->cc = cc;
|
|
if (id != NULL) {
|
|
response->id = util_strdup_s(id);
|
|
}
|
|
if (g_lcrd_errmsg != NULL) {
|
|
response->errmsg = util_strdup_s(g_lcrd_errmsg);
|
|
DAEMON_CLEAR_ERRMSG();
|
|
}
|
|
}
|
|
|
|
static int container_resume_cb(const container_resume_request *request, container_resume_response **response)
|
|
{
|
|
int ret = 0;
|
|
uint32_t cc = LCRD_SUCCESS;
|
|
char *name = NULL;
|
|
char *id = NULL;
|
|
container_t *cont = NULL;
|
|
|
|
DAEMON_CLEAR_ERRMSG();
|
|
if (request == NULL || response == NULL) {
|
|
ERROR("Invalid NULL input");
|
|
return -1;
|
|
}
|
|
|
|
*response = util_common_calloc_s(sizeof(container_resume_response));
|
|
if (*response == NULL) {
|
|
ERROR("Resume: Out of memory");
|
|
cc = LCRD_ERR_MEMOUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
name = request->id;
|
|
if (name == NULL) {
|
|
ERROR("Resume: receive NULL id");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
if (!util_valid_container_id_or_name(name)) {
|
|
ERROR("Invalid container name %s", name);
|
|
lcrd_set_error_message("Invalid container name %s", name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
cont = containers_store_get(name);
|
|
if (cont == NULL) {
|
|
ERROR("No such container:%s", name);
|
|
lcrd_set_error_message("No such container:%s", name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
id = cont->common_config->id;
|
|
set_log_prefix(id);
|
|
EVENT("Event: {Object: %s, Type: Resuming}", id);
|
|
|
|
if (gc_is_gc_progress(id)) {
|
|
lcrd_set_error_message("You cannot resume container %s in garbage collector progress.", id);
|
|
ERROR("You cannot resume container %s in garbage collector progress.", id);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
ret = resume_container(cont);
|
|
if (ret != 0) {
|
|
cc = LCRD_ERR_EXEC;
|
|
container_state_set_error(cont->state, (const char *)g_lcrd_errmsg);
|
|
goto pack_response;
|
|
}
|
|
|
|
EVENT("Event: {Object: %s, Type: Resumed}", id);
|
|
|
|
pack_response:
|
|
pack_resume_response(*response, cc, id);
|
|
container_unref(cont);
|
|
free_log_prefix();
|
|
return (cc == LCRD_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
static int runtime_pause(const char *id, const char *runtime, const char *rootpath)
|
|
{
|
|
int ret = 0;
|
|
struct engine_operation *engine_ops = NULL;
|
|
|
|
engine_ops = engines_get_handler(runtime);
|
|
if (engine_ops == NULL || engine_ops->engine_pause_op == NULL) {
|
|
DEBUG("Failed to get engine pause operations");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!engine_ops->engine_pause_op(id, rootpath)) {
|
|
DEBUG("Pause container %s failed", id);
|
|
const char *tmpmsg = NULL;
|
|
tmpmsg = engine_ops->engine_get_errmsg_op();
|
|
lcrd_set_error_message("Pause container error;%s", (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ?
|
|
tmpmsg : DEF_ERR_RUNTIME_STR);
|
|
engine_ops->engine_clear_errmsg_op();
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int pause_container(container_t *cont)
|
|
{
|
|
int ret = 0;
|
|
const char *id = cont->common_config->id;
|
|
|
|
container_lock(cont);
|
|
|
|
if (!is_running(cont->state)) {
|
|
ERROR("Container %s is not running", id);
|
|
lcrd_set_error_message("Container %s is not running", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (is_paused(cont->state)) {
|
|
ERROR("Container %s is already paused", id);
|
|
lcrd_set_error_message("Container %s is already paused", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (is_restarting(cont->state)) {
|
|
ERROR("Container %s is restarting, wait until the container is running", id);
|
|
lcrd_set_error_message("Container %s is restarting, wait until the container is running", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (runtime_pause(id, cont->runtime, cont->root_path)) {
|
|
ERROR("Failed to pause container:%s", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
state_set_paused(cont->state);
|
|
|
|
if (container_to_disk(cont)) {
|
|
ERROR("Failed to save container \"%s\" to disk", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
out:
|
|
container_unlock(cont);
|
|
return ret;
|
|
}
|
|
|
|
static void pack_pause_response(container_pause_response *response, uint32_t cc, const char *id)
|
|
{
|
|
if (response == NULL) {
|
|
return;
|
|
}
|
|
response->cc = cc;
|
|
if (id != NULL) {
|
|
response->id = util_strdup_s(id);
|
|
}
|
|
if (g_lcrd_errmsg != NULL) {
|
|
response->errmsg = util_strdup_s(g_lcrd_errmsg);
|
|
DAEMON_CLEAR_ERRMSG();
|
|
}
|
|
}
|
|
|
|
static int container_pause_cb(const container_pause_request *request,
|
|
container_pause_response **response)
|
|
{
|
|
int ret = 0;
|
|
uint32_t cc = LCRD_SUCCESS;
|
|
char *name = NULL;
|
|
char *id = NULL;
|
|
container_t *cont = NULL;
|
|
|
|
DAEMON_CLEAR_ERRMSG();
|
|
|
|
if (request == NULL || response == NULL) {
|
|
ERROR("Invalid NULL input");
|
|
return -1;
|
|
}
|
|
|
|
*response = util_common_calloc_s(sizeof(container_pause_response));
|
|
if (*response == NULL) {
|
|
ERROR("Pause: Out of memory");
|
|
cc = LCRD_ERR_MEMOUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
name = request->id;
|
|
|
|
if (name == NULL) {
|
|
ERROR("Pause: receive NULL id");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
if (!util_valid_container_id_or_name(name)) {
|
|
ERROR("Invalid container name %s", name);
|
|
lcrd_set_error_message("Invalid container name %s", name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
cont = containers_store_get(name);
|
|
if (cont == NULL) {
|
|
ERROR("No such container:%s", name);
|
|
lcrd_set_error_message("No such container:%s", name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
id = cont->common_config->id;
|
|
set_log_prefix(id);
|
|
|
|
EVENT("Event: {Object: %s, Type: Pausing}", id);
|
|
|
|
if (gc_is_gc_progress(id)) {
|
|
lcrd_set_error_message("You cannot pause container %s in garbage collector progress.", id);
|
|
ERROR("You cannot pause container %s in garbage collector progress.", id);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
ret = pause_container(cont);
|
|
if (ret != 0) {
|
|
cc = LCRD_ERR_EXEC;
|
|
container_state_set_error(cont->state, (const char *)g_lcrd_errmsg);
|
|
goto pack_response;
|
|
}
|
|
|
|
EVENT("Event: {Object: %s, Type: Paused}", id);
|
|
|
|
update_health_monitor(id);
|
|
|
|
pack_response:
|
|
pack_pause_response(*response, cc, id);
|
|
container_unref(cont);
|
|
free_log_prefix();
|
|
return (cc == LCRD_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
static void to_engine_resources(const host_config *hostconfig, struct engine_cgroup_resources *cr)
|
|
{
|
|
if (hostconfig == NULL || cr == NULL) {
|
|
return;
|
|
}
|
|
|
|
cr->blkio_weight = hostconfig->blkio_weight;
|
|
cr->cpu_shares = (uint64_t)hostconfig->cpu_shares;
|
|
cr->cpu_period = (uint64_t)hostconfig->cpu_period;
|
|
cr->cpu_quota = (uint64_t)hostconfig->cpu_quota;
|
|
cr->cpuset_cpus = hostconfig->cpuset_cpus;
|
|
cr->cpuset_mems = hostconfig->cpuset_mems;
|
|
cr->memory_limit = (uint64_t)hostconfig->memory;
|
|
cr->memory_swap = (uint64_t)hostconfig->memory_swap;
|
|
cr->memory_reservation = (uint64_t)hostconfig->memory_reservation;
|
|
cr->kernel_memory_limit = (uint64_t)hostconfig->kernel_memory;
|
|
}
|
|
|
|
static void host_config_restore_unlocking(container_t *cont, host_config *backup_hostconfig)
|
|
{
|
|
free_host_config(cont->hostconfig);
|
|
cont->hostconfig = backup_hostconfig;
|
|
if (container_to_disk(cont)) {
|
|
ERROR("Failed to save container \"%s\" to disk", cont->common_config->id);
|
|
}
|
|
}
|
|
|
|
static void update_container_cpu(const host_config *hostconfig, host_config *chostconfig)
|
|
{
|
|
if (hostconfig->cpu_shares != 0) {
|
|
chostconfig->cpu_shares = hostconfig->cpu_shares;
|
|
}
|
|
if (hostconfig->cpu_period != 0) {
|
|
chostconfig->cpu_period = hostconfig->cpu_period;
|
|
}
|
|
if (hostconfig->cpu_quota != 0) {
|
|
chostconfig->cpu_quota = hostconfig->cpu_quota;
|
|
}
|
|
if (hostconfig->cpuset_cpus != NULL) {
|
|
free(chostconfig->cpuset_cpus);
|
|
chostconfig->cpuset_cpus = util_strdup_s(hostconfig->cpuset_cpus);
|
|
}
|
|
if (hostconfig->cpuset_mems != NULL) {
|
|
free(chostconfig->cpuset_mems);
|
|
chostconfig->cpuset_mems = util_strdup_s(hostconfig->cpuset_mems);
|
|
}
|
|
}
|
|
|
|
static int update_container_memory(const char *id, const host_config *hostconfig, host_config *chostconfig)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (hostconfig->memory != 0) {
|
|
// if memory limit smaller than already set memoryswap limit and doesn't
|
|
// update the memoryswap limit, then error out.
|
|
if (chostconfig->memory_swap > 0 &&
|
|
hostconfig->memory > chostconfig->memory_swap &&
|
|
hostconfig->memory_swap == 0) {
|
|
ERROR("Memory limit should be smaller than already set memoryswap limit,"
|
|
" update the memoryswap at the same time");
|
|
lcrd_set_error_message("Cannot update container %s: Memory limit should be smaller than"
|
|
"already set memoryswap limit, update the memoryswap at the same time.", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
chostconfig->memory = hostconfig->memory;
|
|
}
|
|
|
|
if (hostconfig->memory_swap != 0) {
|
|
chostconfig->memory_swap = hostconfig->memory_swap;
|
|
}
|
|
if (hostconfig->memory_reservation != 0) {
|
|
chostconfig->memory_reservation = hostconfig->memory_reservation;
|
|
}
|
|
if (hostconfig->kernel_memory != 0) {
|
|
chostconfig->kernel_memory = hostconfig->kernel_memory;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int update_container_restart_policy(const host_config *hostconfig, host_config *chostconfig)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (hostconfig->restart_policy != NULL && hostconfig->restart_policy->name != NULL) {
|
|
free_host_config_restart_policy(chostconfig->restart_policy);
|
|
chostconfig->restart_policy = util_common_calloc_s(sizeof(host_config_restart_policy));
|
|
if (chostconfig->restart_policy == NULL) {
|
|
ERROR("Out of memory");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
chostconfig->restart_policy->name = util_strdup_s(hostconfig->restart_policy->name);
|
|
chostconfig->restart_policy->maximum_retry_count = hostconfig->restart_policy->maximum_retry_count;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int update_container(const container_t *cont, const host_config *hostconfig)
|
|
{
|
|
int ret = 0;
|
|
char *id = NULL;
|
|
host_config *chostconfig = NULL;
|
|
|
|
if (cont == NULL || cont->hostconfig == NULL || hostconfig == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
id = cont->common_config->id;
|
|
|
|
chostconfig = cont->hostconfig;
|
|
|
|
if (hostconfig->blkio_weight != 0) {
|
|
chostconfig->blkio_weight = hostconfig->blkio_weight;
|
|
}
|
|
|
|
update_container_cpu(hostconfig, chostconfig);
|
|
|
|
ret = update_container_memory(id, hostconfig, chostconfig);
|
|
if (ret != 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
ret = update_container_restart_policy(hostconfig, chostconfig);
|
|
if (ret != 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
host_config *dump_host_config(const host_config *origconfig)
|
|
{
|
|
char *json = NULL;
|
|
parser_error err = NULL;
|
|
host_config *newconfig = NULL;
|
|
|
|
if (origconfig == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
json = host_config_generate_json(origconfig, NULL, &err);
|
|
if (json == NULL) {
|
|
ERROR("Failed to generate json: %s", err);
|
|
goto out;
|
|
}
|
|
newconfig = host_config_parse_data(json, NULL, &err);
|
|
if (newconfig == NULL) {
|
|
ERROR("Failed to parse json: %s", err);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
free(err);
|
|
free(json);
|
|
return newconfig;
|
|
}
|
|
|
|
static int update_host_config_check(container_t *cont, host_config *hostconfig)
|
|
{
|
|
int ret = 0;
|
|
const char *id = cont->common_config->id;
|
|
|
|
ret = verify_host_config_settings(hostconfig, true);
|
|
if (ret != 0) {
|
|
goto out;
|
|
}
|
|
|
|
if (is_removal_in_progress(cont->state) || is_dead(cont->state)) {
|
|
ERROR("Container is marked for removal and cannot be \"update\".");
|
|
lcrd_set_error_message("Cannot update container %s: Container is marked for removal and cannot be \"update\".",
|
|
id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (is_running(cont->state) && hostconfig->kernel_memory) {
|
|
ERROR("Can not update kernel memory to a running container, please stop it first.");
|
|
lcrd_set_error_message("Cannot update container %s: Can not update kernel memory to a running container,"
|
|
" please stop it first.", id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int runtime_update(const char *id, const char *runtime, const char *rootpath, struct engine_cgroup_resources *cr)
|
|
{
|
|
int ret = 0;
|
|
struct engine_operation *engine_ops = NULL;
|
|
|
|
engine_ops = engines_get_handler(runtime);
|
|
if (engine_ops == NULL || engine_ops->engine_update_op == NULL) {
|
|
DEBUG("Failed to get engine update operations");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!engine_ops->engine_update_op(id, rootpath, cr)) {
|
|
DEBUG("Update container %s failed", id);
|
|
const char *tmpmsg = NULL;
|
|
tmpmsg = engine_ops->engine_get_errmsg_op();
|
|
lcrd_set_error_message("Cannot update container %s: %s", id, (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ?
|
|
tmpmsg : DEF_ERR_RUNTIME_STR);
|
|
engine_ops->engine_clear_errmsg_op();
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int do_update_resources(const container_update_request *request, container_t *cont)
|
|
{
|
|
int ret = 0;
|
|
const char *id = cont->common_config->id;
|
|
parser_error err = NULL;
|
|
host_config *hostconfig = NULL;
|
|
host_config *backup_hostconfig = NULL;
|
|
struct engine_cgroup_resources cr = { 0 };
|
|
|
|
if (request->host_config == NULL) {
|
|
DEBUG("receive NULL host config");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
hostconfig = host_config_parse_data(request->host_config, NULL, &err);
|
|
if (hostconfig == NULL) {
|
|
ERROR("Failed to parse host config data:%s", err);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
container_lock(cont);
|
|
|
|
if (update_host_config_check(cont, hostconfig)) {
|
|
ret = -1;
|
|
goto unlock_out;
|
|
}
|
|
|
|
backup_hostconfig = dump_host_config(cont->hostconfig);
|
|
if (backup_hostconfig == NULL) {
|
|
ret = -1;
|
|
goto unlock_out;
|
|
}
|
|
|
|
if (update_container(cont, hostconfig)) {
|
|
host_config_restore_unlocking(cont, backup_hostconfig);
|
|
ret = -1;
|
|
goto unlock_out;
|
|
}
|
|
if (container_to_disk(cont)) {
|
|
ERROR("Failed to save container \"%s\" to disk", id);
|
|
host_config_restore_unlocking(cont, backup_hostconfig);
|
|
ret = -1;
|
|
goto unlock_out;
|
|
}
|
|
|
|
if (hostconfig->restart_policy && hostconfig->restart_policy->name) {
|
|
container_update_restart_manager(cont, hostconfig->restart_policy);
|
|
}
|
|
|
|
to_engine_resources(hostconfig, &cr);
|
|
if (runtime_update(id, cont->runtime, cont->root_path, &cr)) {
|
|
ERROR("Update container %s failed", id);
|
|
host_config_restore_unlocking(cont, backup_hostconfig);
|
|
ret = -1;
|
|
goto unlock_out;
|
|
}
|
|
|
|
unlock_out:
|
|
container_unlock(cont);
|
|
out:
|
|
if (ret == 0) {
|
|
free_host_config(backup_hostconfig);
|
|
}
|
|
free_host_config(hostconfig);
|
|
free(err);
|
|
return ret;
|
|
}
|
|
|
|
static void pack_update_response(container_update_response *response, uint32_t cc, const char *id)
|
|
{
|
|
if (response == NULL) {
|
|
return;
|
|
}
|
|
response->cc = cc;
|
|
if (id != NULL) {
|
|
response->id = util_strdup_s(id);
|
|
}
|
|
if (g_lcrd_errmsg != NULL) {
|
|
response->errmsg = util_strdup_s(g_lcrd_errmsg);
|
|
DAEMON_CLEAR_ERRMSG();
|
|
}
|
|
}
|
|
|
|
static int container_update_cb(const container_update_request *request, container_update_response **response)
|
|
{
|
|
uint32_t cc = LCRD_SUCCESS;
|
|
char *container_name = NULL;
|
|
char *id = NULL;
|
|
container_t *cont = NULL;
|
|
|
|
DAEMON_CLEAR_ERRMSG();
|
|
if (request == NULL || response == NULL) {
|
|
ERROR("Invalid NULL input");
|
|
return -1;
|
|
}
|
|
|
|
*response = util_common_calloc_s(sizeof(container_update_response));
|
|
if (*response == NULL) {
|
|
ERROR("Out of memory");
|
|
cc = LCRD_ERR_MEMOUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
container_name = request->name;
|
|
if (container_name == NULL) {
|
|
DEBUG("receive NULL Request id");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
if (!util_valid_container_id_or_name(container_name)) {
|
|
ERROR("Invalid container name %s", container_name);
|
|
lcrd_set_error_message("Invalid container name %s", container_name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
cont = containers_store_get(container_name);
|
|
if (cont == NULL) {
|
|
ERROR("No such container: %s", container_name);
|
|
lcrd_set_error_message("No such container: %s", container_name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
id = cont->common_config->id;
|
|
set_log_prefix(id);
|
|
EVENT("Event: {Object: %s, Type: updating}", id);
|
|
|
|
if (do_update_resources(request, cont) != 0) {
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
EVENT("Event: {Object: %s, Type: updated}", id);
|
|
|
|
pack_response:
|
|
pack_update_response(*response, cc, id);
|
|
|
|
container_unref(cont);
|
|
|
|
free_log_prefix();
|
|
return (cc == LCRD_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
static void pack_export_response(container_export_response *response, uint32_t cc, const char *id)
|
|
{
|
|
if (response == NULL) {
|
|
return;
|
|
}
|
|
response->cc = cc;
|
|
if (id != NULL) {
|
|
response->id = util_strdup_s(id);
|
|
}
|
|
if (g_lcrd_errmsg != NULL) {
|
|
response->errmsg = util_strdup_s(g_lcrd_errmsg);
|
|
DAEMON_CLEAR_ERRMSG();
|
|
}
|
|
}
|
|
|
|
static int container_export_cb(const container_export_request *request, container_export_response **response)
|
|
{
|
|
int ret = 0;
|
|
char *name = NULL;
|
|
char *id = NULL;
|
|
char *file = NULL;
|
|
uint32_t cc = LCRD_SUCCESS;
|
|
container_t *cont = NULL;
|
|
|
|
DAEMON_CLEAR_ERRMSG();
|
|
if (request == NULL || response == NULL) {
|
|
ERROR("Invalid NULL input");
|
|
return -1;
|
|
}
|
|
|
|
*response = util_common_calloc_s(sizeof(container_export_response));
|
|
if (*response == NULL) {
|
|
ERROR("Export: Out of memory");
|
|
cc = LCRD_ERR_MEMOUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
name = request->id;
|
|
file = request->file;
|
|
|
|
if (name == NULL) {
|
|
ERROR("Export: receive NULL id");
|
|
cc = LCRD_ERR_INPUT;
|
|
goto pack_response;
|
|
}
|
|
|
|
if (!util_valid_container_id_or_name(name)) {
|
|
ERROR("Invalid container name %s", name);
|
|
lcrd_set_error_message("Invalid container name %s", name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
cont = containers_store_get(name);
|
|
if (cont == NULL) {
|
|
ERROR("No such container:%s", name);
|
|
lcrd_set_error_message("No such container:%s", name);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
id = cont->common_config->id;
|
|
set_log_prefix(id);
|
|
|
|
if (gc_is_gc_progress(id)) {
|
|
lcrd_set_error_message("You cannot export container %s in garbage collector progress.", id);
|
|
ERROR("You cannot export container %s in garbage collector progress.", id);
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
ret = export_container(cont, file);
|
|
if (ret != 0) {
|
|
cc = LCRD_ERR_EXEC;
|
|
goto pack_response;
|
|
}
|
|
|
|
pack_response:
|
|
pack_export_response(*response, cc, id);
|
|
container_unref(cont);
|
|
free_log_prefix();
|
|
return (cc == LCRD_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
void container_extend_callback_init(service_container_callback_t *cb)
|
|
{
|
|
cb->update = container_update_cb;
|
|
cb->pause = container_pause_cb;
|
|
cb->resume = container_resume_cb;
|
|
cb->stats = container_stats_cb;
|
|
cb->events = container_events_cb;
|
|
cb->export_rootfs = container_export_cb;
|
|
}
|
|
|