X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=ntsimulator%2Fntsim-ng%2Fcore%2Fdocker.c;fp=ntsimulator%2Fntsim-ng%2Fcore%2Fdocker.c;h=40f2bff1af68dcdfdcb7d53829e5d0690a9ab3a4;hb=f1d5c9198acde7a7ce296490087cad37e008f688;hp=0000000000000000000000000000000000000000;hpb=f2d8f1002fa93848c80a88e5658db4816cba3020;p=sim%2Fo1-interface.git diff --git a/ntsimulator/ntsim-ng/core/docker.c b/ntsimulator/ntsim-ng/core/docker.c new file mode 100644 index 0000000..40f2bff --- /dev/null +++ b/ntsimulator/ntsim-ng/core/docker.c @@ -0,0 +1,756 @@ +/************************************************************************* +* +* 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; +}