1 /*************************************************************************
3 * Copyright 2020 highstreet technologies GmbH and others
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 ***************************************************************************/
21 #include "utils/log_utils.h"
22 #include "utils/sys_utils.h"
23 #include "utils/http_client.h"
24 #include "core/framework.h"
25 #include "core/session.h"
26 #include "core/context.h"
30 #include <sys/sysinfo.h>
32 #include <cjson/cJSON.h>
34 #define DOCKER_SOCK_FNAME "/var/run/docker.sock"
36 static cJSON *docker_network_info = 0;
38 struct installable_module {
50 static environment_var_t *docker_environment_var;
51 static int docker_environment_var_count = 0;
53 static int get_installable_modules(struct installable_module **modules); //list available modules for install
54 static void list_yangs(const char *path, struct installable_module **modules, int *total);
56 static char *docker_parse_json_message(const char *json_string);
58 static int docker_container_create(const char *image, manager_network_function_instance_t *instance);
59 static int docker_container_start(manager_network_function_instance_t *instance);
60 static int docker_container_inspect(manager_network_function_instance_t *instance);
63 bool docker_container_init(void) {
66 sr_log_stderr(SR_LL_NONE);
67 log_message(1, "Entering container-init mode...\n");
70 rc = sr_connect(0, &session_connection);
72 log_error("sr_connect failed");
77 session_context = (struct ly_ctx *)sr_get_context(session_connection);
78 if(session_context == 0) {
79 log_error("sr_get_context failed");
83 /* install yang files */
84 log_message(1, "Installing yang files...\n");
85 struct installable_module *modules;
86 int total_modules = get_installable_modules(&modules);
87 log_message(1, "Found total modules: %d\n", total_modules);
89 int old_failed_installations = 1;
90 int failed_installations = 0;
91 int install_round = 0;
92 while(failed_installations != old_failed_installations) {
93 old_failed_installations = failed_installations;
94 failed_installations = 0;
96 for(int i = 0; i < total_modules; i++) {
97 if(!modules[i].installed) {
98 modules[i].submodule = context_yang_is_module(modules[i].fullpath);
99 if(!modules[i].submodule) {
100 if(!framework_is_docker_excluded_module(modules[i].name)) {
101 log_message(1, "[round %d] trying to install module %s from %s... ", install_round, modules[i].name, modules[i].fullpath);
102 if(!context_module_install(modules[i].name, modules[i].fullpath)) {
103 failed_installations++;
104 log_message(1, LOG_COLOR_BOLD_YELLOW"failed"LOG_COLOR_RESET"\n");
107 log_message(1, LOG_COLOR_BOLD_GREEN"done"LOG_COLOR_RESET"\n");
108 modules[i].installed = true;
112 log_message(1, "[round %d] not installing module %s as it's excluded in config.\n", install_round, modules[i].name);
113 modules[i].installed = true;
117 log_message(1, "[round %d] %s is a submodule... "LOG_COLOR_BOLD_YELLOW"skipping"LOG_COLOR_RESET"\n", install_round, modules[i].name);
118 modules[i].installed = true;
124 if(failed_installations != 0) {
125 log_error("Failed to install all modules in %d rounds...", install_round);
129 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));
132 //set access for all installed modules
133 log_message(1, "Setting access configuration for installed modules... ");
134 for(int i = 0; i < total_modules; i++) {
135 if((!framework_is_docker_excluded_module(modules[i].name)) && (!modules[i].submodule)) {
136 if(!context_module_set_access(modules[i].name)) {
137 log_error("Failed to set access to module %s...", modules[i].name);
142 log_message(1, LOG_COLOR_BOLD_GREEN"done"LOG_COLOR_RESET"\n");
144 //cleanup module-install used memory
145 for(int i = 0; i < total_modules; i++) {
146 free(modules[i].name);
147 free(modules[i].fullpath);
152 session_context = (struct ly_ctx *)sr_get_context(session_connection);
153 if(session_context == 0) {
154 log_error("sr_get_context failed");
158 //init context so we can see all the available modules, features, etc
159 rc = context_init(session_context);
161 log_error("context_init() failed");
165 /* enable features */
166 log_message(1, "Enabling yang features...\n");
167 char **available_features;
168 int total_available_features;
169 total_available_features = context_get_features(&available_features);
170 log_message(1, "Found total features: %d\n", total_available_features);
171 for(int i = 0; i < total_available_features; i++) {
172 log_message(1, "feature %s: ", available_features[i]);
174 if(!context_get_feature_enabled(available_features[i])) {
175 if(!framework_is_docker_excluded_feature(available_features[i])) {
176 if(context_feature_enable(available_features[i])) {
177 log_message(1, "enabling... "LOG_COLOR_BOLD_GREEN"done"LOG_COLOR_RESET"\n");
180 log_error("enabling... failed\n");
184 log_message(1, "excluded in config, skipping\n");
188 log_message(1, "already "LOG_COLOR_BOLD_GREEN"enabled"LOG_COLOR_RESET", skipping.\n");
191 for(int i = 0; i < total_available_features; i++) {
192 free(available_features[i]);
194 free(available_features);
196 sr_disconnect(session_connection);
199 log_message(1, LOG_COLOR_BOLD_GREEN"ntsim successfully initialized Docker container"LOG_COLOR_RESET"\n");
203 static int get_installable_modules(struct installable_module **modules) {
206 list_yangs("/opt/dev/deploy/yang", modules, &total);
210 static void list_yangs(const char *path, struct installable_module **modules, int *total) {
215 while((dir = readdir(d)) != NULL) {
216 if(dir->d_type == DT_DIR) {
217 if(strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0)
220 snprintf(new_path, sizeof(new_path), "%s/%s", path, dir->d_name);
221 list_yangs(new_path, modules, total);
224 if(strstr(dir->d_name, ".yang") != 0) {
225 *modules = (struct installable_module *)realloc(*modules, sizeof(struct installable_module) * (*total + 1));
227 log_error("could not realloc");
231 (*modules)[*total].name = (char*)malloc(sizeof(char) * (strlen(dir->d_name) + 1));
232 if(!(*modules)[*total].name) {
233 log_error("could not alloc");
236 strcpy((*modules)[*total].name, dir->d_name);
237 (*modules)[*total].name[strlen(dir->d_name) - 5] = 0; //extract ".yang"
238 char *rev = strstr((*modules)[*total].name, "@");
239 if(rev) { //extract revision, if exists
243 (*modules)[*total].fullpath = (char*)malloc(sizeof(char) * (strlen(path) + 1 + strlen(dir->d_name) + 1));
244 if(!(*modules)[*total].fullpath) {
245 log_error("could not alloc");
248 sprintf((*modules)[*total].fullpath, "%s/%s", path, dir->d_name);
250 (*modules)[*total].installed = false;
251 (*modules)[*total].submodule = false;
261 int docker_device_init(void) {
264 asprintf(&url, "http://v%s/containers/%s/json", framework_environment.docker_engine_version, framework_environment.hostname);
266 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", 0, 0, &response);
267 if(rc != NTS_ERR_OK) {
268 log_error("http_socket_request failed");
269 return NTS_ERR_FAILED;
272 cJSON *json_response = cJSON_Parse(response);
275 if(json_response == 0) {
276 log_error("could not parse JSON response for url=\"%s\"", url);
277 return NTS_ERR_FAILED;
280 cJSON *hostConfig = cJSON_GetObjectItemCaseSensitive(json_response, "HostConfig");
281 if(hostConfig == 0) {
282 log_error("could not get HostConfig object");
283 return NTS_ERR_FAILED;
286 cJSON *networkMode = cJSON_GetObjectItemCaseSensitive(hostConfig, "NetworkMode");
287 if(networkMode == 0) {
288 log_error("could not get NetworkMode object");
289 return NTS_ERR_FAILED;
292 docker_network_info = cJSON_Duplicate(networkMode, 1);
293 cJSON_Delete(json_response);
295 log_message(2, "finished parsing docker inspect...\n");
298 docker_environment_var_count = 5;
299 docker_environment_var = (environment_var_t *)malloc(sizeof(environment_var_t) * docker_environment_var_count);
300 if(docker_environment_var == 0) {
301 log_error("malloc failed");
302 return NTS_ERR_FAILED;
305 //set env variables for network functions
306 docker_environment_var[0].name = ENV_VAR_SSH_CONNECTIONS;
307 asprintf(&docker_environment_var[0].value, "%d", framework_environment.ssh_connections);
308 docker_environment_var[1].name = ENV_VAR_TLS_CONNECTIONS;
309 asprintf(&docker_environment_var[1].value, "%d", framework_environment.tls_connections);
310 docker_environment_var[2].name = ENV_VAR_IPV6ENABLED;
311 docker_environment_var[2].value = framework_environment.ip_v6_enabled ? "true" : "false";
312 docker_environment_var[3].name = ENV_VAR_HOST_IP;
313 docker_environment_var[3].value = framework_environment.host_ip;
314 docker_environment_var[4].name = ENV_VAR_HOST_BASE_PORT;
315 // docker_environment_var[4].value = will be updated by docker_create...
320 int docker_device_start(const manager_network_function_type *function_type, manager_network_function_instance_t *instance) {
321 assert(function_type);
323 assert(docker_network_info);
326 if(function_type->docker_version_tag && (function_type->docker_version_tag[0] != 0)) {
327 if(function_type->docker_repository && (function_type->docker_repository[0] != 0)) {
328 asprintf(&image, "%s/%s:%s", function_type->docker_repository, function_type->docker_image_name, function_type->docker_version_tag);
331 asprintf(&image, "%s:%s", function_type->docker_image_name, function_type->docker_version_tag);
335 if(function_type->docker_repository && (function_type->docker_repository[0] != 0)) {
336 asprintf(&image, "%s/%s:latest", function_type->docker_repository, function_type->docker_image_name);
339 asprintf(&image, "%s:latest", function_type->docker_image_name);
343 int rc = docker_container_create(image, instance);
344 if(rc != NTS_ERR_OK) {
345 log_error("docker_container_create failed");
346 return NTS_ERR_FAILED;
350 rc = docker_container_start(instance);
351 if(rc != NTS_ERR_OK) {
352 log_error("docker_container_start failed");
353 return NTS_ERR_FAILED;
356 rc = docker_container_inspect(instance);
357 if(rc != NTS_ERR_OK) {
358 log_error("docker_container_inspect failed");
359 return NTS_ERR_FAILED;
362 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);
367 int docker_device_stop(manager_network_function_instance_t *instance) {
371 asprintf(&url, "http://v%s/containers/%s?force=true", framework_environment.docker_engine_version, instance->docker_id);
373 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "DELETE", "", 0, 0);
375 if(rc != NTS_ERR_OK) {
376 log_error("http_socket_request failed");
377 return NTS_ERR_FAILED;
383 docker_usage_t docker_usage_get(const manager_network_function_type *function_type, int function_type_count) {
389 char full_text[1024 * 1024];
390 FILE* pipe = popen("docker stats --no-stream --format \"table {{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|\"", "r");
392 log_error("popen() failed");
399 n = fread(buffer, 1, sizeof(buffer), pipe);
400 for(int i = 0; i < n; i++) {
401 full_text[k++] = buffer[i];
414 char *d = strstr(c + 1, "\n");
416 for(char *i = c + 1; i < d; i++) {
417 line[i - c - 1] = *i;
421 char container_name[1024];
426 char *x = strstr(line, "|");
427 for(char *i = line; i < x; i++) {
428 container_name[i - line] = *i;
429 container_name[i - line + 1] = 0;
433 x = strstr(start, "|");
434 for(char *i = start; i < x; i++) {
435 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
436 buff[i - start] = *i;
444 cpu = strtof(buff, 0);
448 x = strstr(start, "|");
449 for(char *i = start; i < x; i++) {
450 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
451 buff[i - start] = *i;
462 mem = strtof(buff, 0) * mul;
465 if(strcmp(container_name, framework_environment.hostname) == 0) {
470 for(int i = 0; i < function_type_count; i++) {
471 for(int j = 0; j < function_type[i].started_instances; j++) {
473 if(strcmp(container_name, function_type[i].instance[j].docker_id) == 0) {
487 ret.cpu /= get_nprocs();
492 static char *docker_parse_json_message(const char *json_string) {
495 cJSON *json_response = cJSON_Parse(json_string);
496 if(json_response == 0) {
497 log_error("cJSON_Parse failed");
502 message = cJSON_GetObjectItem(json_response, "message");
504 log_error("json parsing failed");
508 char *ret = strdup(message->valuestring);
509 cJSON_Delete(json_response);
513 static int docker_container_create(const char *image, manager_network_function_instance_t *instance) {
517 cJSON *postDataJson = cJSON_CreateObject();
518 if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) {
519 log_error("could not create JSON object: Image");
520 return NTS_ERR_FAILED;
523 if(cJSON_AddStringToObject(postDataJson, "Hostname", instance->name) == 0) {
524 log_error("could not create JSON object: Hostname");
525 return NTS_ERR_FAILED;
528 cJSON *hostConfig = cJSON_CreateObject();
529 if(hostConfig == 0) {
530 log_error("could not create JSON object: HostConfig");
531 return NTS_ERR_FAILED;
533 cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig);
535 cJSON *portBindings = cJSON_CreateObject();
536 if(portBindings == 0) {
537 printf("could not create JSON object: PortBindings");
538 return NTS_ERR_FAILED;
540 cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings);
542 for(int i = 0; i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections + framework_environment.sftp_connections); ++i) {
543 cJSON *port = cJSON_CreateArray();
545 log_error("could not create JSON object: port");
546 return NTS_ERR_FAILED;
549 char dockerContainerPort[20];
550 if(i < framework_environment.ssh_connections + framework_environment.tls_connections) {
551 sprintf(dockerContainerPort, "%d/tcp", STANDARD_NETCONF_PORT + i);
553 else if(i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections)) {
554 sprintf(dockerContainerPort, "%d/tcp", STANDARD_FTP_PORT);
556 else if(i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections + framework_environment.sftp_connections)) {
557 sprintf(dockerContainerPort, "%d/tcp", STANDARD_SFTP_PORT);
559 cJSON_AddItemToObject(portBindings, dockerContainerPort, port);
561 cJSON *hostPort = cJSON_CreateObject();
563 log_error("could not create JSON object: HostPort");
564 return NTS_ERR_FAILED;
567 char dockerHostPort[20];
568 sprintf(dockerHostPort, "%d", instance->host_port + i);
569 if(cJSON_AddStringToObject(hostPort, "HostPort", dockerHostPort) == 0) {
570 log_error("could not create JSON object: HostPortString");
571 return NTS_ERR_FAILED;
574 if(cJSON_AddStringToObject(hostPort, "HostIp", "0.0.0.0") == 0) { //instance->host_ip
575 log_error("could not create JSON object: HostIpString");
576 return NTS_ERR_FAILED;
579 cJSON_AddItemToArray(port, hostPort);
583 //environment vars start
584 asprintf(&docker_environment_var[4].value, "%d", instance->host_port);
586 cJSON *env_variables_array = cJSON_CreateArray();
587 if (env_variables_array == 0) {
588 log_error("Could not create JSON object: Env array");
589 return NTS_ERR_FAILED;
591 cJSON_AddItemToObject(postDataJson, "Env", env_variables_array);
593 for(int i = 0; i < docker_environment_var_count; i++) {
594 char *environment_var = 0;
595 asprintf(&environment_var, "%s=%s", docker_environment_var[i].name, docker_environment_var[i].value);
597 cJSON *env_var_obj = cJSON_CreateString(environment_var);
598 if(env_var_obj == 0) {
599 log_error("could not create JSON object");
600 return NTS_ERR_FAILED;
602 cJSON_AddItemToArray(env_variables_array, env_var_obj);
604 free(environment_var);
607 free(docker_environment_var[4].value);
608 //environment vars finished
611 cJSON *netMode = cJSON_Duplicate(docker_network_info, 1);
612 cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
614 char *post_data_string = 0;
615 post_data_string = cJSON_PrintUnformatted(postDataJson);
616 cJSON_Delete(postDataJson);
619 asprintf(&url, "http:/v%s/containers/create?name=%s", framework_environment.docker_engine_version, instance->name);
622 int response_code = 0;
623 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response);
624 if(rc != NTS_ERR_OK) {
625 log_error("http_socket_request failed");
626 return NTS_ERR_FAILED;
630 if(response_code != 201) {
631 char *message = docker_parse_json_message(response);
632 log_error("docker_container_create failed (%d): %s", response_code, message);
635 return NTS_ERR_FAILED;
638 cJSON *json_response = cJSON_Parse(response);
640 const cJSON *container_id = 0;
642 container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
644 if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) {
645 char container_id_short[13];
646 memset(container_id_short, '\0', sizeof(container_id_short));
647 strncpy(container_id_short, container_id->valuestring, 12);
649 instance->docker_id = strdup(container_id_short);
651 cJSON_Delete(json_response);
655 cJSON_Delete(json_response);
656 return NTS_ERR_FAILED;
661 static int docker_container_start(manager_network_function_instance_t *instance) {
665 asprintf(&url, "http://v%s/containers/%s/start", framework_environment.docker_engine_version, instance->docker_id);
668 int response_code = 0;
669 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", "", &response_code, &response);
671 if(rc != NTS_ERR_OK) {
672 log_error("http_socket_request failed");
673 return NTS_ERR_FAILED;
676 if(response_code == 304) {
677 log_error("docker_container_start failed (%d): container already started\n", response_code);
679 return NTS_ERR_FAILED;
681 else if(response_code != 204) {
682 char *message = docker_parse_json_message(response);
683 log_error("docker_container_start failed (%d): %s", response_code, message);
686 return NTS_ERR_FAILED;
694 static int docker_container_inspect(manager_network_function_instance_t *instance) {
698 asprintf(&url, "http://v%s/containers/%s/json", framework_environment.docker_engine_version, instance->docker_id);
701 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
703 if(rc != NTS_ERR_OK) {
704 log_error("http_socket_request failed");
705 return NTS_ERR_FAILED;
708 cJSON *json_response = cJSON_Parse(response);
710 if(json_response == 0) {
711 log_error("cJSON_Parse failed");
712 return NTS_ERR_FAILED;
716 cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings");
718 log_error("json parsing failed");
719 return NTS_ERR_FAILED;
722 cJSON *node = cJSON_GetObjectItem(main_node, "Networks");
724 log_error("json parsing failed");
725 return NTS_ERR_FAILED;
728 node = node->child; //get info from the first in array
730 log_error("json parsing failed");
731 return NTS_ERR_FAILED;
734 char *ipv6_env_var = getenv("IPv6_ENABLED");
735 if(ipv6_env_var == 0) {
736 log_error("could not get the IPv6 Enabled env variable");
737 return NTS_ERR_FAILED;
740 cJSON *element = cJSON_GetObjectItem(node, "IPAddress");
741 if(strcmp(ipv6_env_var, "true") == 0) {
742 element = cJSON_GetObjectItem(node, "GlobalIPv6Address");
745 element = cJSON_GetObjectItem(node, "IPAddress");
749 log_error("json parsing failed");
750 return NTS_ERR_FAILED;
752 instance->docker_ip = strdup(element->valuestring);
754 cJSON_Delete(json_response);