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 sprintf(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 cJSON_Delete(json_response);
284 return NTS_ERR_FAILED;
287 cJSON *networkMode = cJSON_GetObjectItemCaseSensitive(hostConfig, "NetworkMode");
288 if(networkMode == 0) {
289 log_error("could not get NetworkMode object");
290 cJSON_Delete(json_response);
291 return NTS_ERR_FAILED;
294 docker_network_info = cJSON_Duplicate(networkMode, 1);
295 cJSON_Delete(json_response);
297 log_message(2, "finished parsing docker inspect...\n");
300 docker_environment_var_count = 5;
301 docker_environment_var = (environment_var_t *)malloc(sizeof(environment_var_t) * docker_environment_var_count);
302 if(docker_environment_var == 0) {
303 log_error("malloc failed");
304 cJSON_Delete(networkMode);
305 return NTS_ERR_FAILED;
308 //set env variables for network functions
309 docker_environment_var[0].name = ENV_VAR_SSH_CONNECTIONS;
310 asprintf(&docker_environment_var[0].value, "%d", framework_environment.ssh_connections);
311 docker_environment_var[1].name = ENV_VAR_TLS_CONNECTIONS;
312 asprintf(&docker_environment_var[1].value, "%d", framework_environment.tls_connections);
313 docker_environment_var[2].name = ENV_VAR_IPV6ENABLED;
314 docker_environment_var[2].value = framework_environment.ip_v6_enabled ? "true" : "false";
315 docker_environment_var[3].name = ENV_VAR_HOST_IP;
316 docker_environment_var[3].value = framework_environment.host_ip;
317 docker_environment_var[4].name = ENV_VAR_HOST_BASE_PORT;
318 // docker_environment_var[4].value = will be updated by docker_create...
323 int docker_device_start(const manager_network_function_type *function_type, manager_network_function_instance_t *instance) {
324 assert(function_type);
326 assert(docker_network_info);
329 if(function_type->docker_version_tag && (function_type->docker_version_tag[0] != 0)) {
330 if(function_type->docker_repository && (function_type->docker_repository[0] != 0) && (strcmp(function_type->docker_repository, "local") != 0)) {
331 sprintf(image, "%s/%s:%s", function_type->docker_repository, function_type->docker_image_name, function_type->docker_version_tag);
334 sprintf(image, "%s:%s", function_type->docker_image_name, function_type->docker_version_tag);
338 if(function_type->docker_repository && (function_type->docker_repository[0] != 0) && (strcmp(function_type->docker_repository, "local") != 0)) {
339 sprintf(image, "%s/%s:latest", function_type->docker_repository, function_type->docker_image_name);
342 sprintf(image, "%s:latest", function_type->docker_image_name);
346 int rc = docker_container_create(image, instance);
347 if(rc != NTS_ERR_OK) {
348 log_error("docker_container_create failed");
349 return NTS_ERR_FAILED;
352 rc = docker_container_start(instance);
353 if(rc != NTS_ERR_OK) {
354 log_error("docker_container_start failed");
355 docker_device_stop(instance);
356 return NTS_ERR_FAILED;
359 rc = docker_container_inspect(instance);
360 if(rc != NTS_ERR_OK) {
361 log_error("docker_container_inspect failed");
362 docker_device_stop(instance);
363 return NTS_ERR_FAILED;
366 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);
371 int docker_device_stop(manager_network_function_instance_t *instance) {
375 sprintf(url, "http://v%s/containers/%s?force=true", framework_environment.docker_engine_version, instance->docker_id);
377 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "DELETE", "", 0, 0);
378 if(rc != NTS_ERR_OK) {
379 log_error("http_socket_request failed");
380 return NTS_ERR_FAILED;
386 docker_usage_t docker_usage_get(const manager_network_function_type *function_type, int function_type_count) {
392 char full_text[1024 * 1024];
393 FILE* pipe = popen("docker stats --no-stream --format \"table {{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|\"", "r");
395 log_error("popen() failed");
402 n = fread(buffer, 1, sizeof(buffer), pipe);
403 for(int i = 0; i < n; i++) {
404 full_text[k++] = buffer[i];
417 char *d = strstr(c + 1, "\n");
419 for(char *i = c + 1; i < d; i++) {
420 line[i - c - 1] = *i;
424 char container_name[1024];
429 char *x = strstr(line, "|");
430 for(char *i = line; i < x; i++) {
431 container_name[i - line] = *i;
432 container_name[i - line + 1] = 0;
436 x = strstr(start, "|");
437 for(char *i = start; i < x; i++) {
438 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
439 buff[i - start] = *i;
447 cpu = strtof(buff, 0);
451 x = strstr(start, "|");
452 for(char *i = start; i < x; i++) {
453 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
454 buff[i - start] = *i;
465 mem = strtof(buff, 0) * mul;
468 if(strcmp(container_name, framework_environment.hostname) == 0) {
473 for(int i = 0; i < function_type_count; i++) {
474 for(int j = 0; j < function_type[i].started_instances; j++) {
476 if(strcmp(container_name, function_type[i].instance[j].docker_id) == 0) {
490 ret.cpu /= get_nprocs();
495 static char *docker_parse_json_message(const char *json_string) {
498 cJSON *json_response = cJSON_Parse(json_string);
499 if(json_response == 0) {
500 log_error("cJSON_Parse failed");
505 message = cJSON_GetObjectItem(json_response, "message");
507 log_error("json parsing failed");
508 cJSON_Delete(json_response);
512 char *ret = strdup(message->valuestring);
513 cJSON_Delete(json_response);
517 static int docker_container_create(const char *image, manager_network_function_instance_t *instance) {
521 cJSON *postDataJson = cJSON_CreateObject();
522 if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) {
523 log_error("could not create JSON object: Image");
524 return NTS_ERR_FAILED;
527 if(cJSON_AddStringToObject(postDataJson, "Hostname", instance->name) == 0) {
528 log_error("could not create JSON object: Hostname");
529 cJSON_Delete(postDataJson);
530 return NTS_ERR_FAILED;
533 cJSON *hostConfig = cJSON_CreateObject();
534 if(hostConfig == 0) {
535 log_error("could not create JSON object: HostConfig");
536 cJSON_Delete(postDataJson);
537 return NTS_ERR_FAILED;
539 if(cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig) == 0) {
540 log_error("cJSON_AddItemToObject failed");
541 cJSON_Delete(postDataJson);
542 return NTS_ERR_FAILED;
545 cJSON *portBindings = cJSON_CreateObject();
546 if(portBindings == 0) {
547 printf("could not create JSON object: PortBindings");
548 cJSON_Delete(postDataJson);
549 return NTS_ERR_FAILED;
551 if(cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings) == 0) {
552 log_error("cJSON_AddItemToObject failed");
553 cJSON_Delete(postDataJson);
554 return NTS_ERR_FAILED;
557 for(int i = 0; i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections + framework_environment.sftp_connections); ++i) {
558 cJSON *port = cJSON_CreateArray();
560 log_error("could not create JSON object: port");
561 cJSON_Delete(postDataJson);
562 return NTS_ERR_FAILED;
565 char dockerContainerPort[20];
566 if(i < framework_environment.ssh_connections + framework_environment.tls_connections) {
567 sprintf(dockerContainerPort, "%d/tcp", STANDARD_NETCONF_PORT + i);
569 else if(i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections)) {
570 sprintf(dockerContainerPort, "%d/tcp", STANDARD_FTP_PORT);
572 else if(i < (framework_environment.ssh_connections + framework_environment.tls_connections + framework_environment.ftp_connections + framework_environment.sftp_connections)) {
573 sprintf(dockerContainerPort, "%d/tcp", STANDARD_SFTP_PORT);
575 if(cJSON_AddItemToObject(portBindings, dockerContainerPort, port) == 0) {
576 log_error("cJSON_AddItemToObject failed");
577 cJSON_Delete(postDataJson);
578 return NTS_ERR_FAILED;
581 cJSON *hostPort = cJSON_CreateObject();
583 log_error("could not create JSON object: HostPort");
584 cJSON_Delete(postDataJson);
585 return NTS_ERR_FAILED;
588 char dockerHostPort[20];
589 sprintf(dockerHostPort, "%d", instance->host_port + i);
590 if(cJSON_AddStringToObject(hostPort, "HostPort", dockerHostPort) == 0) {
591 log_error("could not create JSON object: HostPortString");
592 cJSON_Delete(postDataJson);
593 return NTS_ERR_FAILED;
596 if(cJSON_AddStringToObject(hostPort, "HostIp", "0.0.0.0") == 0) { //instance->host_ip
597 log_error("could not create JSON object: HostIpString");
598 cJSON_Delete(postDataJson);
599 return NTS_ERR_FAILED;
602 if(cJSON_AddItemToArray(port, hostPort) == 0) {
603 log_error("cJSON_AddItemToArray failed");
604 cJSON_Delete(postDataJson);
605 return NTS_ERR_FAILED;
610 //environment vars start
611 asprintf(&docker_environment_var[4].value, "%d", instance->host_port);
613 cJSON *env_variables_array = cJSON_CreateArray();
614 if(env_variables_array == 0) {
615 log_error("Could not create JSON object: Env array");
616 return NTS_ERR_FAILED;
618 cJSON_AddItemToObject(postDataJson, "Env", env_variables_array);
620 for(int i = 0; i < docker_environment_var_count; i++) {
621 if(docker_environment_var[i].value) {
622 char *environment_var = 0;
623 asprintf(&environment_var, "%s=%s", docker_environment_var[i].name, docker_environment_var[i].value);
625 cJSON *env_var_obj = cJSON_CreateString(environment_var);
626 if(env_var_obj == 0) {
627 log_error("could not create JSON object");
628 return NTS_ERR_FAILED;
630 cJSON_AddItemToArray(env_variables_array, env_var_obj);
632 free(environment_var);
636 free(docker_environment_var[4].value);
637 //environment vars finished
640 cJSON *netMode = cJSON_Duplicate(docker_network_info, 1);
641 cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
643 char *post_data_string = 0;
644 post_data_string = cJSON_PrintUnformatted(postDataJson);
645 cJSON_Delete(postDataJson);
648 sprintf(url, "http:/v%s/containers/create?name=%s", framework_environment.docker_engine_version, instance->name);
651 int response_code = 0;
652 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response);
653 free(post_data_string);
654 if(rc != NTS_ERR_OK) {
655 log_error("http_socket_request failed");
656 return NTS_ERR_FAILED;
659 if(response_code != 201) {
660 char *message = docker_parse_json_message(response);
661 log_error("docker_container_create failed (%d): %s", response_code, message);
664 return NTS_ERR_FAILED;
667 cJSON *json_response = cJSON_Parse(response);
669 const cJSON *container_id = 0;
671 container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
673 if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) {
674 char container_id_short[13];
675 memset(container_id_short, '\0', sizeof(container_id_short));
676 strncpy(container_id_short, container_id->valuestring, 12);
678 instance->docker_id = strdup(container_id_short);
680 cJSON_Delete(json_response);
684 cJSON_Delete(json_response);
685 return NTS_ERR_FAILED;
690 static int docker_container_start(manager_network_function_instance_t *instance) {
694 sprintf(url, "http://v%s/containers/%s/start", framework_environment.docker_engine_version, instance->docker_id);
697 int response_code = 0;
698 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", "", &response_code, &response);
699 if(rc != NTS_ERR_OK) {
700 log_error("http_socket_request failed");
701 return NTS_ERR_FAILED;
704 if(response_code == 304) {
705 log_error("docker_container_start failed (%d): container already started\n", response_code);
707 return NTS_ERR_FAILED;
709 else if(response_code != 204) {
710 char *message = docker_parse_json_message(response);
711 log_error("docker_container_start failed (%d): %s", response_code, message);
714 return NTS_ERR_FAILED;
722 static int docker_container_inspect(manager_network_function_instance_t *instance) {
726 sprintf(url, "http://v%s/containers/%s/json", framework_environment.docker_engine_version, instance->docker_id);
729 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
730 if(rc != NTS_ERR_OK) {
731 log_error("http_socket_request failed");
732 return NTS_ERR_FAILED;
735 cJSON *json_response = cJSON_Parse(response);
737 if(json_response == 0) {
738 log_error("cJSON_Parse failed");
739 return NTS_ERR_FAILED;
743 cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings");
745 log_error("json parsing failed");
746 cJSON_Delete(json_response);
747 return NTS_ERR_FAILED;
750 cJSON *node = cJSON_GetObjectItem(main_node, "Networks");
752 log_error("json parsing failed");
753 cJSON_Delete(json_response);
754 return NTS_ERR_FAILED;
757 node = node->child; //get info from the first in array
759 log_error("json parsing failed");
760 cJSON_Delete(json_response);
761 return NTS_ERR_FAILED;
765 if(framework_environment.ip_v6_enabled) {
766 element = cJSON_GetObjectItem(node, "GlobalIPv6Address");
769 element = cJSON_GetObjectItem(node, "IPAddress");
773 log_error("json parsing failed");
774 cJSON_Delete(json_response);
775 return NTS_ERR_FAILED;
778 instance->docker_ip = strdup(element->valuestring);
780 cJSON_Delete(json_response);