/************************************************************************* * * Copyright 2020 highstreet technologies GmbH and others * * 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. ***************************************************************************/ #define _GNU_SOURCE #include "docker.h" #include "utils/log_utils.h" #include "utils/sys_utils.h" #include "utils/http_client.h" #include "core/framework.h" #include "core/session.h" #include "core/context.h" #include #include #include #include #include #define DOCKER_SOCK_FNAME "/var/run/docker.sock" static cJSON *docker_network_info = 0; struct installable_module { char *name; char *fullpath; bool installed; bool submodule; }; typedef struct { char *name; char *value; } environment_var_t; static environment_var_t *docker_environment_var; static int docker_environment_var_count = 0; static int get_installable_modules(struct installable_module **modules); //list available modules for install static void list_yangs(const char *path, struct installable_module **modules, int *total); static char *docker_parse_json_message(const char *json_string); static int docker_container_create(const char *image, manager_network_function_instance_t *instance); static int docker_container_start(manager_network_function_instance_t *instance); static int docker_container_inspect(manager_network_function_instance_t *instance); bool docker_container_init(void) { int rc; sr_log_stderr(SR_LL_NONE); log_message(1, "Entering container-init mode...\n"); // connect to sysrepo rc = sr_connect(0, &session_connection); if(SR_ERR_OK != rc) { log_error("sr_connect failed"); return false; } /* get context */ session_context = (struct ly_ctx *)sr_get_context(session_connection); if(session_context == 0) { log_error("sr_get_context failed"); return false; } /* install yang files */ log_message(1, "Installing yang files...\n"); struct installable_module *modules; int total_modules = get_installable_modules(&modules); log_message(1, "Found total modules: %d\n", total_modules); int old_failed_installations = 1; int failed_installations = 0; int install_round = 0; while(failed_installations != old_failed_installations) { old_failed_installations = failed_installations; failed_installations = 0; install_round++; for(int i = 0; i < total_modules; i++) { if(!modules[i].installed) { modules[i].submodule = context_yang_is_module(modules[i].fullpath); if(!modules[i].submodule) { if(!framework_is_docker_excluded_module(modules[i].name)) { log_message(1, "[round %d] trying to install module %s from %s... ", install_round, modules[i].name, modules[i].fullpath); if(!context_module_install(modules[i].name, modules[i].fullpath)) { failed_installations++; log_message(1, LOG_COLOR_BOLD_YELLOW"failed"LOG_COLOR_RESET"\n"); } else { log_message(1, LOG_COLOR_BOLD_GREEN"done"LOG_COLOR_RESET"\n"); modules[i].installed = true; } } else { log_message(1, "[round %d] not installing module %s as it's excluded in config.\n", install_round, modules[i].name); modules[i].installed = true; } } else { log_message(1, "[round %d] %s is a submodule... "LOG_COLOR_BOLD_YELLOW"skipping"LOG_COLOR_RESET"\n", install_round, modules[i].name); modules[i].installed = true; } } } } if(failed_installations != 0) { log_error("Failed to install all modules in %d rounds...", install_round); return false; } else { log_message(1, LOG_COLOR_BOLD_GREEN"successfully"LOG_COLOR_RESET" installed "LOG_COLOR_BOLD_GREEN"ALL"LOG_COLOR_RESET" modules in "LOG_COLOR_BOLD_YELLOW"%d"LOG_COLOR_RESET" rounds\n", (install_round - 1)); } //set access for all installed modules log_message(1, "Setting access configuration for installed modules... "); for(int i = 0; i < total_modules; i++) { if((!framework_is_docker_excluded_module(modules[i].name)) && (!modules[i].submodule)) { if(!context_module_set_access(modules[i].name)) { log_error("Failed to set access to module %s...", modules[i].name); return false; } } } log_message(1, LOG_COLOR_BOLD_GREEN"done"LOG_COLOR_RESET"\n"); //cleanup module-install used memory for(int i = 0; i < total_modules; i++) { free(modules[i].name); free(modules[i].fullpath); } free(modules); //get context session_context = (struct ly_ctx *)sr_get_context(session_connection); if(session_context == 0) { log_error("sr_get_context failed"); return false; } //init context so we can see all the available modules, features, etc rc = context_init(session_context); if(rc != 0) { log_error("context_init() failed"); return false; } /* enable features */ log_message(1, "Enabling yang features...\n"); char **available_features; int total_available_features; total_available_features = context_get_features(&available_features); log_message(1, "Found total features: %d\n", total_available_features); for(int i = 0; i < total_available_features; i++) { log_message(1, "feature %s: ", available_features[i]); if(!context_get_feature_enabled(available_features[i])) { if(!framework_is_docker_excluded_feature(available_features[i])) { if(context_feature_enable(available_features[i])) { log_message(1, "enabling... "LOG_COLOR_BOLD_GREEN"done"LOG_COLOR_RESET"\n"); } else { log_error("enabling... failed\n"); } } else { log_message(1, "excluded in config, skipping\n"); } } else { log_message(1, "already "LOG_COLOR_BOLD_GREEN"enabled"LOG_COLOR_RESET", skipping.\n"); } } for(int i = 0; i < total_available_features; i++) { free(available_features[i]); } free(available_features); sr_disconnect(session_connection); context_free(); log_message(1, LOG_COLOR_BOLD_GREEN"ntsim successfully initialized Docker container"LOG_COLOR_RESET"\n"); return true; } static int get_installable_modules(struct installable_module **modules) { int total = 0; *modules = 0; list_yangs("/opt/dev/deploy/yang", modules, &total); return total; } static void list_yangs(const char *path, struct installable_module **modules, int *total) { DIR *d; struct dirent *dir; d = opendir(path); if(d) { while((dir = readdir(d)) != NULL) { if(dir->d_type == DT_DIR) { if(strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0) { char new_path[1024]; snprintf(new_path, sizeof(new_path), "%s/%s", path, dir->d_name); list_yangs(new_path, modules, total); } } else { if(strstr(dir->d_name, ".yang") != 0) { *modules = (struct installable_module *)realloc(*modules, sizeof(struct installable_module) * (*total + 1)); if(!*modules) { log_error("could not realloc"); return; } (*modules)[*total].name = (char*)malloc(sizeof(char) * (strlen(dir->d_name) + 1)); if(!(*modules)[*total].name) { log_error("could not alloc"); return; } strcpy((*modules)[*total].name, dir->d_name); (*modules)[*total].name[strlen(dir->d_name) - 5] = 0; //extract ".yang" char *rev = strstr((*modules)[*total].name, "@"); if(rev) { //extract revision, if exists *rev = 0; } (*modules)[*total].fullpath = (char*)malloc(sizeof(char) * (strlen(path) + 1 + strlen(dir->d_name) + 1)); if(!(*modules)[*total].fullpath) { log_error("could not alloc"); return; } sprintf((*modules)[*total].fullpath, "%s/%s", path, dir->d_name); (*modules)[*total].installed = false; (*modules)[*total].submodule = false; (*total)++; } } } closedir(d); } } int docker_device_init(void) { char *response = 0; char *url = 0; asprintf(&url, "http://v%s/containers/%s/json", framework_environment.docker_engine_version, framework_environment.hostname); int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", 0, 0, &response); if(rc != NTS_ERR_OK) { log_error("http_socket_request failed"); return NTS_ERR_FAILED; } cJSON *json_response = cJSON_Parse(response); free(response); if(json_response == 0) { log_error("could not parse JSON response for url=\"%s\"", url); return NTS_ERR_FAILED; } cJSON *hostConfig = cJSON_GetObjectItemCaseSensitive(json_response, "HostConfig"); if(hostConfig == 0) { log_error("could not get HostConfig object"); return NTS_ERR_FAILED; } cJSON *networkMode = cJSON_GetObjectItemCaseSensitive(hostConfig, "NetworkMode"); if(networkMode == 0) { log_error("could not get NetworkMode object"); return NTS_ERR_FAILED; } docker_network_info = cJSON_Duplicate(networkMode, 1); cJSON_Delete(json_response); log_message(2, "finished parsing docker inspect...\n"); docker_environment_var_count = 5; docker_environment_var = (environment_var_t *)malloc(sizeof(environment_var_t) * docker_environment_var_count); if(docker_environment_var == 0) { log_error("malloc failed"); return NTS_ERR_FAILED; } //set env variables for network functions docker_environment_var[0].name = ENV_VAR_SSH_CONNECTIONS; asprintf(&docker_environment_var[0].value, "%d", framework_environment.ssh_connections); docker_environment_var[1].name = ENV_VAR_TLS_CONNECTIONS; asprintf(&docker_environment_var[1].value, "%d", framework_environment.tls_connections); docker_environment_var[2].name = ENV_VAR_IPV6ENABLED; docker_environment_var[2].value = framework_environment.ip_v6_enabled ? "true" : "false"; docker_environment_var[3].name = ENV_VAR_HOST_IP; docker_environment_var[3].value = framework_environment.host_ip; docker_environment_var[4].name = ENV_VAR_HOST_BASE_PORT; // docker_environment_var[4].value = will be updated by docker_create... return NTS_ERR_OK; } int docker_device_start(const manager_network_function_type *function_type, manager_network_function_instance_t *instance) { assert(function_type); assert(instance); assert(docker_network_info); char *image = 0; if(function_type->docker_version_tag && (function_type->docker_version_tag[0] != 0)) { if(function_type->docker_repository && (function_type->docker_repository[0] != 0)) { asprintf(&image, "%s/%s:%s", function_type->docker_repository, function_type->docker_image_name, function_type->docker_version_tag); } else { asprintf(&image, "%s:%s", function_type->docker_image_name, function_type->docker_version_tag); } } else { if(function_type->docker_repository && (function_type->docker_repository[0] != 0)) { asprintf(&image, "%s/%s:latest", function_type->docker_repository, function_type->docker_image_name); } else { asprintf(&image, "%s:latest", function_type->docker_image_name); } } int rc = docker_container_create(image, instance); if(rc != NTS_ERR_OK) { log_error("docker_container_create failed"); return NTS_ERR_FAILED; } free(image); rc = docker_container_start(instance); if(rc != NTS_ERR_OK) { log_error("docker_container_start failed"); return NTS_ERR_FAILED; } rc = docker_container_inspect(instance); if(rc != NTS_ERR_OK) { log_error("docker_container_inspect failed"); return NTS_ERR_FAILED; } log_message(2, "docker_device_start: docker_id: %s | name: %s | docker_ip: %s | host_port: %d\n", instance->docker_id, instance->name, instance->docker_ip, instance->host_port); return NTS_ERR_OK; } int docker_device_stop(manager_network_function_instance_t *instance) { assert(instance); char *url = 0; asprintf(&url, "http://v%s/containers/%s?force=true", framework_environment.docker_engine_version, instance->docker_id); int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "DELETE", "", 0, 0); free(url); if(rc != NTS_ERR_OK) { log_error("http_socket_request failed"); return NTS_ERR_FAILED; } return NTS_ERR_OK; } docker_usage_t docker_usage_get(const manager_network_function_type *function_type, int function_type_count) { docker_usage_t ret; ret.cpu = 0; ret.mem = 0; char buffer[1024]; char full_text[1024 * 1024]; FILE* pipe = popen("docker stats --no-stream --format \"table {{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|\"", "r"); if (!pipe) { log_error("popen() failed"); return ret; } int n = 1; int k = 0; while(n != 0) { n = fread(buffer, 1, sizeof(buffer), pipe); for(int i = 0; i < n; i++) { full_text[k++] = buffer[i]; } } pclose(pipe); full_text[k] = 0; char *c = full_text; c = strstr(c, "\n"); while(c) { char line[1024]; line[0] = 0; char *d = strstr(c + 1, "\n"); if(d) { for(char *i = c + 1; i < d; i++) { line[i - c - 1] = *i; line[i - c] = 0; } char container_name[1024]; char buff[1024]; float cpu = 0.0; float mem = 0.0; char *x = strstr(line, "|"); for(char *i = line; i < x; i++) { container_name[i - line] = *i; container_name[i - line + 1] = 0; } char *start = x + 1; x = strstr(start, "|"); for(char *i = start; i < x; i++) { if(((*i >= '0') && (*i <= '9')) || (*i == '.')) { buff[i - start] = *i; } else { buff[i - start] = 0; break; } } cpu = strtof(buff, 0); int mul = 1; start = x + 1; x = strstr(start, "|"); for(char *i = start; i < x; i++) { if(((*i >= '0') && (*i <= '9')) || (*i == '.')) { buff[i - start] = *i; } else { if(*i == 'G') { mul = 1024; } buff[i - start] = 0; break; } } mem = strtof(buff, 0) * mul; if(strcmp(container_name, framework_environment.hostname) == 0) { ret.cpu += cpu; ret.mem += mem; } else { for(int i = 0; i < function_type_count; i++) { for(int j = 0; j < function_type[i].started_instances; j++) { if(strcmp(container_name, function_type[i].instance[j].docker_id) == 0) { ret.cpu += cpu; ret.mem += mem; break; } } } } } c = d; } ret.cpu /= get_nprocs(); return ret; } static char *docker_parse_json_message(const char *json_string) { assert(json_string); cJSON *json_response = cJSON_Parse(json_string); if(json_response == 0) { log_error("cJSON_Parse failed"); return 0; } cJSON *message; message = cJSON_GetObjectItem(json_response, "message"); if(message == 0) { log_error("json parsing failed"); return 0; } char *ret = strdup(message->valuestring); cJSON_Delete(json_response); return ret; } static int docker_container_create(const char *image, manager_network_function_instance_t *instance) { assert(image); assert(instance); cJSON *postDataJson = cJSON_CreateObject(); if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) { log_error("could not create JSON object: Image"); return NTS_ERR_FAILED; } if(cJSON_AddStringToObject(postDataJson, "Hostname", instance->name) == 0) { log_error("could not create JSON object: Hostname"); return NTS_ERR_FAILED; } cJSON *hostConfig = cJSON_CreateObject(); if(hostConfig == 0) { log_error("could not create JSON object: HostConfig"); return NTS_ERR_FAILED; } cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig); cJSON *portBindings = cJSON_CreateObject(); if(portBindings == 0) { printf("could not create JSON object: PortBindings"); return NTS_ERR_FAILED; } cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings); for(int i = 0; i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections + framework_environment.sftp_connections); ++i) { cJSON *port = cJSON_CreateArray(); if(port == 0) { log_error("could not create JSON object: port"); return NTS_ERR_FAILED; } char dockerContainerPort[20]; if(i < framework_environment.ssh_connections + framework_environment.tls_connections) { sprintf(dockerContainerPort, "%d/tcp", STANDARD_NETCONF_PORT + i); } else if(i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections)) { sprintf(dockerContainerPort, "%d/tcp", STANDARD_FTP_PORT); } else if(i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections + framework_environment.sftp_connections)) { sprintf(dockerContainerPort, "%d/tcp", STANDARD_SFTP_PORT); } cJSON_AddItemToObject(portBindings, dockerContainerPort, port); cJSON *hostPort = cJSON_CreateObject(); if(hostPort == 0) { log_error("could not create JSON object: HostPort"); return NTS_ERR_FAILED; } char dockerHostPort[20]; sprintf(dockerHostPort, "%d", instance->host_port + i); if(cJSON_AddStringToObject(hostPort, "HostPort", dockerHostPort) == 0) { log_error("could not create JSON object: HostPortString"); return NTS_ERR_FAILED; } if(cJSON_AddStringToObject(hostPort, "HostIp", "0.0.0.0") == 0) { //instance->host_ip log_error("could not create JSON object: HostIpString"); return NTS_ERR_FAILED; } cJSON_AddItemToArray(port, hostPort); } //environment vars start asprintf(&docker_environment_var[4].value, "%d", instance->host_port); cJSON *env_variables_array = cJSON_CreateArray(); if (env_variables_array == 0) { log_error("Could not create JSON object: Env array"); return NTS_ERR_FAILED; } cJSON_AddItemToObject(postDataJson, "Env", env_variables_array); for(int i = 0; i < docker_environment_var_count; i++) { char *environment_var = 0; asprintf(&environment_var, "%s=%s", docker_environment_var[i].name, docker_environment_var[i].value); cJSON *env_var_obj = cJSON_CreateString(environment_var); if(env_var_obj == 0) { log_error("could not create JSON object"); return NTS_ERR_FAILED; } cJSON_AddItemToArray(env_variables_array, env_var_obj); free(environment_var); } free(docker_environment_var[4].value); //environment vars finished cJSON *netMode = cJSON_Duplicate(docker_network_info, 1); cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode); char *post_data_string = 0; post_data_string = cJSON_PrintUnformatted(postDataJson); cJSON_Delete(postDataJson); char *url = 0; asprintf(&url, "http:/v%s/containers/create?name=%s", framework_environment.docker_engine_version, instance->name); char *response = 0; int response_code = 0; int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response); if(rc != NTS_ERR_OK) { log_error("http_socket_request failed"); return NTS_ERR_FAILED; } free(url); if(response_code != 201) { char *message = docker_parse_json_message(response); log_error("docker_container_create failed (%d): %s", response_code, message); free(message); free(response); return NTS_ERR_FAILED; } else { cJSON *json_response = cJSON_Parse(response); free(response); const cJSON *container_id = 0; container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id"); if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) { char container_id_short[13]; memset(container_id_short, '\0', sizeof(container_id_short)); strncpy(container_id_short, container_id->valuestring, 12); instance->docker_id = strdup(container_id_short); cJSON_Delete(json_response); return NTS_ERR_OK; } else { cJSON_Delete(json_response); return NTS_ERR_FAILED; } } } static int docker_container_start(manager_network_function_instance_t *instance) { assert(instance); char *url = 0; asprintf(&url, "http://v%s/containers/%s/start", framework_environment.docker_engine_version, instance->docker_id); char *response = 0; int response_code = 0; int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", "", &response_code, &response); free(url); if(rc != NTS_ERR_OK) { log_error("http_socket_request failed"); return NTS_ERR_FAILED; } else { if(response_code == 304) { log_error("docker_container_start failed (%d): container already started\n", response_code); free(response); return NTS_ERR_FAILED; } else if(response_code != 204) { char *message = docker_parse_json_message(response); log_error("docker_container_start failed (%d): %s", response_code, message); free(message); free(response); return NTS_ERR_FAILED; } } return NTS_ERR_OK; } static int docker_container_inspect(manager_network_function_instance_t *instance) { assert(instance); char *url = 0; asprintf(&url, "http://v%s/containers/%s/json", framework_environment.docker_engine_version, instance->docker_id); char *response = 0; int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response); free(url); if(rc != NTS_ERR_OK) { log_error("http_socket_request failed"); return NTS_ERR_FAILED; } cJSON *json_response = cJSON_Parse(response); free(response); if(json_response == 0) { log_error("cJSON_Parse failed"); return NTS_ERR_FAILED; } cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings"); if(main_node == 0) { log_error("json parsing failed"); return NTS_ERR_FAILED; } cJSON *node = cJSON_GetObjectItem(main_node, "Networks"); if(node == 0) { log_error("json parsing failed"); return NTS_ERR_FAILED; } node = node->child; //get info from the first in array if(node == 0) { log_error("json parsing failed"); return NTS_ERR_FAILED; } char *ipv6_env_var = getenv("IPv6_ENABLED"); if(ipv6_env_var == 0) { log_error("could not get the IPv6 Enabled env variable"); return NTS_ERR_FAILED; } cJSON *element = cJSON_GetObjectItem(node, "IPAddress"); if(strcmp(ipv6_env_var, "true") == 0) { element = cJSON_GetObjectItem(node, "GlobalIPv6Address"); } else { element = cJSON_GetObjectItem(node, "IPAddress"); } if(element == 0) { log_error("json parsing failed"); return NTS_ERR_FAILED; } instance->docker_ip = strdup(element->valuestring); cJSON_Delete(json_response); return NTS_ERR_OK; }