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/nts_utils.h"
24 #include "utils/http_client.h"
25 #include "core/framework.h"
26 #include "core/session.h"
27 #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;
43 static environment_var_t *docker_environment_var;
44 static int docker_environment_var_count = 0;
46 static char *docker_parse_json_message(const char *json_string);
47 static int docker_add_port(cJSON *portBindings, uint16_t docker_port, uint16_t host_port);
49 static int docker_populate_images(docker_context_t *context, int count, const char *min_version);
50 static int docker_container_create(const char *image, docker_container_t *container);
51 static int docker_container_start(docker_container_t *container);
52 static int docker_container_inspect(docker_container_t *container);
54 int docker_init(const char **filter, int filter_count, const char *min_version, docker_context_t **context) {
61 sprintf(url, "http://v%s/containers/%s/json", framework_environment.settings.docker_engine_version, framework_environment.settings.hostname);
63 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", 0, 0, &response);
64 if(rc != NTS_ERR_OK) {
65 log_error("http_socket_request failed\n");
66 return NTS_ERR_FAILED;
69 cJSON *json_response = cJSON_Parse(response);
72 if(json_response == 0) {
73 log_error("could not parse JSON response for url=\"%s\"\n", url);
74 return NTS_ERR_FAILED;
77 cJSON *hostConfig = cJSON_GetObjectItemCaseSensitive(json_response, "HostConfig");
79 log_error("could not get HostConfig object\n");
80 cJSON_Delete(json_response);
81 return NTS_ERR_FAILED;
84 cJSON *networkMode = cJSON_GetObjectItemCaseSensitive(hostConfig, "NetworkMode");
85 if(networkMode == 0) {
86 log_error("could not get NetworkMode object\n");
87 cJSON_Delete(json_response);
88 return NTS_ERR_FAILED;
91 docker_network_info = cJSON_Duplicate(networkMode, 1);
92 cJSON_Delete(json_response);
94 log_add_verbose(2, "finished parsing docker inspect...\n");
96 docker_environment_var_count = 9;
97 docker_environment_var = (environment_var_t *)malloc(sizeof(environment_var_t) * docker_environment_var_count);
98 if(docker_environment_var == 0) {
99 log_error("malloc failed\n");
100 cJSON_Delete(networkMode);
101 return NTS_ERR_FAILED;
104 //set env variables for network functions
105 docker_environment_var[0].name = ENV_VAR_SSH_CONNECTIONS;
106 asprintf(&docker_environment_var[0].value, "%d", framework_environment.settings.ssh_connections);
107 docker_environment_var[1].name = ENV_VAR_TLS_CONNECTIONS;
108 asprintf(&docker_environment_var[1].value, "%d", framework_environment.settings.tls_connections);
109 docker_environment_var[2].name = ENV_VAR_IPV6ENABLED;
110 docker_environment_var[2].value = framework_environment.settings.ip_v6_enabled ? "true" : "false";
111 docker_environment_var[3].name = ENV_VAR_HOST_IP;
112 docker_environment_var[3].value = framework_environment.host.ip;
114 docker_environment_var[4].name = ENV_VAR_HOST_NETCONF_SSH_BASE_PORT;
115 // docker_environment_var[4].value = will be updated by docker_create...
116 docker_environment_var[5].name = ENV_VAR_HOST_NETCONF_TLS_BASE_PORT;
117 // docker_environment_var[5].value = will be updated by docker_create...
118 docker_environment_var[6].name = ENV_VAR_HOST_TRANSFER_FTP_BASE_PORT;
119 // docker_environment_var[6].value = will be updated by docker_create...
120 docker_environment_var[7].name = ENV_VAR_HOST_TRANSFER_SFTP_BASE_PORT;
121 // docker_environment_var[7].value = will be updated by docker_create...
123 docker_environment_var[8].name = ENV_VAR_VES_COMMON_HEADER_VERSION;
124 docker_environment_var[8].value = framework_environment.ves_endpoint.common_header_version;
128 //docker context build
129 *context = (docker_context_t *)malloc(sizeof(docker_context_t) * filter_count);
131 log_error("bad malloc\n");
132 free(docker_environment_var[0].value);
133 free(docker_environment_var[1].value);
134 free(docker_environment_var);
135 return NTS_ERR_FAILED;
138 docker_context_t *ctx = *context;
139 for(int i = 0; i < filter_count; i++) {
140 ctx[i].image = strdup(filter[i]);
141 ctx[i].available_images = 0;
142 ctx[i].available_images_count = 0;
145 docker_populate_images(ctx, filter_count, min_version);
150 void docker_free(docker_context_t *context, int count) {
151 free(docker_environment_var[0].value);
152 free(docker_environment_var[1].value);
153 free(docker_environment_var);
155 for(int i = 0; i < count; i++) {
156 free(context[i].image);
157 for(int j = 0; j < context[i].available_images_count; j++) {
158 free(context[i].available_images[j].repo);
159 free(context[i].available_images[j].tag);
161 free(context[i].available_images);
165 int docker_start(const char *container_name, const char *tag, const char *image, const char *repo, uint16_t host_netconf_ssh_port, uint16_t host_netconf_tls_port, uint16_t host_ftp_port, uint16_t host_sftp_port, docker_container_t *container) {
166 assert(container_name);
169 assert(docker_network_info);
171 char image_full[512];
172 if(tag && (tag[0] != 0)) {
173 if(repo && (repo[0] != 0) && (strcmp(repo, "local") != 0)) {
174 sprintf(image_full, "%s/%s:%s", repo, image, tag);
177 sprintf(image_full, "%s:%s", image, tag);
181 if(repo && (repo[0] != 0) && (strcmp(repo, "local") != 0)) {
182 sprintf(image_full, "%s/%s:latest", repo, image);
185 sprintf(image_full, "%s:latest", image);
189 container->name = strdup(container_name);
191 container->docker_ip = 0;
192 container->docker_netconf_ssh_port = STANDARD_NETCONF_PORT;
193 container->docker_netconf_tls_port = container->docker_netconf_ssh_port + framework_environment.settings.ssh_connections;
194 if(framework_environment.settings.ssh_connections == 0) {
195 container->docker_netconf_ssh_port = 0;
197 if(framework_environment.settings.tls_connections == 0) {
198 container->docker_netconf_tls_port = 0;
200 container->docker_ftp_port= STANDARD_FTP_PORT;
201 container->docker_sftp_port= STANDARD_SFTP_PORT;
203 container->host_ip = strdup(framework_environment.host.ip);
204 container->host_netconf_ssh_port = host_netconf_ssh_port;
205 container->host_netconf_tls_port = host_netconf_tls_port;
206 container->host_ftp_port = host_ftp_port;
207 container->host_sftp_port = host_sftp_port;
209 int rc = docker_container_create(image_full, container);
210 if(rc != NTS_ERR_OK) {
211 log_error("docker_container_create failed\n");
212 return NTS_ERR_FAILED;
215 rc = docker_container_start(container);
216 if(rc != NTS_ERR_OK) {
217 log_error("docker_container_start failed\n");
218 docker_stop(container);
219 return NTS_ERR_FAILED;
222 rc = docker_container_inspect(container);
223 if(rc != NTS_ERR_OK) {
224 log_error("docker_container_inspect failed\n");
225 docker_stop(container);
226 return NTS_ERR_FAILED;
229 log_add_verbose(2, "docker_start: name: %s | id: %s | docker_ip: %s | netconf_ssh_port: (%d:%d) | netconf_tls_port: (%d:%d) | ftp_port: (%d:%d) | sftp_port: (%d:%d)\n", container->name, container->id, container->docker_ip, container->docker_netconf_ssh_port, container->host_netconf_ssh_port, container->docker_netconf_tls_port, container->host_netconf_tls_port, container->docker_ftp_port, container->host_ftp_port, container->docker_sftp_port, container->host_sftp_port);
234 int docker_stop(docker_container_t *container) {
238 sprintf(url, "http://v%s/containers/%s?force=true", framework_environment.settings.docker_engine_version, container->id);
240 free(container->name);
242 free(container->docker_ip);
243 free(container->host_ip);
245 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "DELETE", "", 0, 0);
246 if(rc != NTS_ERR_OK) {
247 log_error("http_socket_request failed\n");
248 return NTS_ERR_FAILED;
254 int docker_usage_get(const char **instances_id, int count, docker_usage_t *usage) {
255 assert(instances_id);
262 char full_text[1024 * 1024];
263 FILE* pipe = popen("docker stats --no-stream --format \"table {{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|\"", "r");
265 log_error("popen() failed\n");
266 return NTS_ERR_FAILED;
272 n = fread(buffer, 1, sizeof(buffer), pipe);
273 for(int i = 0; i < n; i++) {
274 full_text[k++] = buffer[i];
287 char *d = strstr(c + 1, "\n");
289 for(char *i = c + 1; i < d; i++) {
290 line[i - c - 1] = *i;
294 char container_name[1024];
299 char *x = strstr(line, "|");
300 for(char *i = line; i < x; i++) {
301 container_name[i - line] = *i;
302 container_name[i - line + 1] = 0;
306 x = strstr(start, "|");
307 for(char *i = start; i < x; i++) {
308 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
309 buff[i - start] = *i;
317 cpu = strtof(buff, 0);
321 x = strstr(start, "|");
322 for(char *i = start; i < x; i++) {
323 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
324 buff[i - start] = *i;
335 mem = strtof(buff, 0) * mul;
338 if(strcmp(container_name, framework_environment.settings.hostname) == 0) {
343 for(int i = 0; i < count; i++) {
344 if(strcmp(container_name, instances_id[i]) == 0) {
356 usage->cpu /= get_nprocs();
361 int docker_pull(const char *repo, const char *image, const char *tag) {
366 char image_full[256];
367 if(tag && (tag[0] != 0)) {
368 sprintf(image_full, "%s/%s:%s", repo, image, tag);
371 sprintf(image_full, "%s/%s:latest", repo, image);
375 sprintf(url, "http:/v%s/images/create?fromImage=%s", framework_environment.settings.docker_engine_version, image_full);
378 int response_code = 0;
379 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", 0, &response_code, &response);
381 if(rc != NTS_ERR_OK) {
382 log_error("http_socket_request failed\n");
383 return NTS_ERR_FAILED;
386 if(response_code != 200) {
387 char *message = docker_parse_json_message(response);
388 log_error("docker_pull failed (%d): %s\n", response_code, message);
391 return NTS_ERR_FAILED;
397 static char *docker_parse_json_message(const char *json_string) {
400 cJSON *json_response = cJSON_Parse(json_string);
401 if(json_response == 0) {
402 log_error("cJSON_Parse failed\n");
406 cJSON *message = cJSON_GetObjectItem(json_response, "message");
408 log_error("json parsing failed\n");
409 cJSON_Delete(json_response);
413 char *ret = strdup(message->valuestring);
414 cJSON_Delete(json_response);
418 static int docker_add_port(cJSON *portBindings, uint16_t docker_port, uint16_t host_port) {
419 assert(portBindings);
421 cJSON *port = cJSON_CreateArray();
423 log_error("could not create JSON object: port\n");
424 return NTS_ERR_FAILED;
427 char dockerContainerPort[20];
428 sprintf(dockerContainerPort, "%d/tcp", docker_port);
430 if(cJSON_AddItemToObject(portBindings, dockerContainerPort, port) == 0) {
431 log_error("cJSON_AddItemToObject failed\n");
432 return NTS_ERR_FAILED;
435 cJSON *hostPort = cJSON_CreateObject();
437 log_error("could not create JSON object: HostPort\n");
438 return NTS_ERR_FAILED;
441 char dockerHostPort[20];
442 sprintf(dockerHostPort, "%d", host_port);
443 if(cJSON_AddStringToObject(hostPort, "HostPort", dockerHostPort) == 0) {
444 log_error("could not create JSON object: HostPortString\n");
445 cJSON_Delete(hostPort);
446 return NTS_ERR_FAILED;
449 if(cJSON_AddItemToArray(port, hostPort) == 0) {
450 log_error("cJSON_AddItemToArray failed\n");
451 cJSON_Delete(hostPort);
452 return NTS_ERR_FAILED;
458 static int docker_populate_images(docker_context_t *context, int count, const char *min_version) {
464 sprintf(url, "http://v%s/images/json", framework_environment.settings.docker_engine_version);
467 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
468 if(rc != NTS_ERR_OK) {
469 log_error("http_socket_request failed\n");
470 return NTS_ERR_FAILED;
473 cJSON *json_response = cJSON_Parse(response);
475 if(json_response == 0) {
476 log_error("cJSON_Parse failed\n");
477 return NTS_ERR_FAILED;
481 cJSON_ArrayForEach(element, json_response) {
482 cJSON *tag = cJSON_GetObjectItem(element, "RepoTags");
485 cJSON_ArrayForEach(ctag, tag) {
486 char *tag_name = ctag->valuestring; //contains repo/image:tag
487 for(int i = 0; i < count; i++) {
488 char *s = strstr(tag_name, context[i].image);
490 char *tag = s + strlen(context[i].image);
492 tag = strdup(s + strlen(context[i].image) + 1);
501 if(nts_vercmp(tag, min_version) >= 0) {
504 repo = strdup(tag_name);
505 *(strstr(repo, context[i].image) - 1) = 0;
511 context[i].available_images = (docker_available_images_t *)realloc(context[i].available_images, (sizeof(docker_available_images_t) * (context[i].available_images_count + 1)));
512 context[i].available_images[context[i].available_images_count].repo = repo;
513 context[i].available_images[context[i].available_images_count].tag = tag;
514 context[i].available_images_count++;
525 cJSON_Delete(json_response);
530 static int docker_container_create(const char *image, docker_container_t *container) {
534 cJSON *postDataJson = cJSON_CreateObject();
535 if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) {
536 log_error("could not create JSON object: Image\n");
537 return NTS_ERR_FAILED;
540 if(cJSON_AddStringToObject(postDataJson, "Hostname", container->name) == 0) {
541 log_error("could not create JSON object: Hostname\n");
542 cJSON_Delete(postDataJson);
543 return NTS_ERR_FAILED;
546 cJSON *hostConfig = cJSON_CreateObject();
547 if(hostConfig == 0) {
548 log_error("could not create JSON object: HostConfig\n");
549 cJSON_Delete(postDataJson);
550 return NTS_ERR_FAILED;
552 if(cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig) == 0) {
553 log_error("cJSON_AddItemToObject failed\n");
554 cJSON_Delete(postDataJson);
555 return NTS_ERR_FAILED;
558 cJSON *portBindings = cJSON_CreateObject();
559 if(portBindings == 0) {
560 printf("could not create JSON object: PortBindings");
561 cJSON_Delete(postDataJson);
562 return NTS_ERR_FAILED;
564 if(cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings) == 0) {
565 log_error("cJSON_AddItemToObject failed\n");
566 cJSON_Delete(postDataJson);
567 return NTS_ERR_FAILED;
570 for(int i = 0; i < framework_environment.settings.ssh_connections; i++) {
571 if(docker_add_port(portBindings, container->docker_netconf_ssh_port + i, container->host_netconf_ssh_port + i) != NTS_ERR_OK) {
572 log_error("docker_add_port() failed\n");
573 cJSON_Delete(postDataJson);
574 return NTS_ERR_FAILED;
578 for(int i = 0; i < framework_environment.settings.tls_connections; i++) {
579 if(docker_add_port(portBindings, container->docker_netconf_tls_port + i, container->host_netconf_tls_port + i) != NTS_ERR_OK) {
580 log_error("docker_add_port() failed\n");
581 cJSON_Delete(postDataJson);
582 return NTS_ERR_FAILED;
586 for(int i = 0; i < framework_environment.settings.ftp_connections; i++) {
587 if(docker_add_port(portBindings, container->docker_ftp_port + i, container->host_ftp_port + i) != NTS_ERR_OK) {
588 log_error("docker_add_port() failed\n");
589 cJSON_Delete(postDataJson);
590 return NTS_ERR_FAILED;
594 for(int i = 0; i < framework_environment.settings.sftp_connections; i++) {
595 if(docker_add_port(portBindings, container->docker_sftp_port + i, container->host_sftp_port + i) != NTS_ERR_OK) {
596 log_error("docker_add_port() failed\n");
597 cJSON_Delete(postDataJson);
598 return NTS_ERR_FAILED;
602 //environment vars start
603 asprintf(&docker_environment_var[4].value, "%d", container->host_netconf_ssh_port);
604 asprintf(&docker_environment_var[5].value, "%d", container->host_netconf_tls_port);
605 asprintf(&docker_environment_var[6].value, "%d", container->host_ftp_port);
606 asprintf(&docker_environment_var[7].value, "%d", container->host_sftp_port);
608 cJSON *env_variables_array = cJSON_CreateArray();
609 if(env_variables_array == 0) {
610 log_error("Could not create JSON object: Env array\n");
611 cJSON_Delete(postDataJson);
612 free(docker_environment_var[4].value);
613 free(docker_environment_var[5].value);
614 free(docker_environment_var[6].value);
615 free(docker_environment_var[7].value);
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\n");
628 cJSON_Delete(postDataJson);
629 free(docker_environment_var[4].value);
630 free(docker_environment_var[5].value);
631 free(docker_environment_var[6].value);
632 free(docker_environment_var[7].value);
633 free(environment_var);
634 return NTS_ERR_FAILED;
636 cJSON_AddItemToArray(env_variables_array, env_var_obj);
638 free(environment_var);
642 free(docker_environment_var[4].value);
643 free(docker_environment_var[5].value);
644 free(docker_environment_var[6].value);
645 free(docker_environment_var[7].value);
646 //environment vars finished
648 cJSON *netMode = cJSON_Duplicate(docker_network_info, 1);
649 cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
651 char *post_data_string = 0;
652 post_data_string = cJSON_PrintUnformatted(postDataJson);
653 cJSON_Delete(postDataJson);
656 sprintf(url, "http:/v%s/containers/create?name=%s", framework_environment.settings.docker_engine_version, container->name);
659 int response_code = 0;
660 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response);
661 free(post_data_string);
662 if(rc != NTS_ERR_OK) {
663 log_error("http_socket_request failed\n");
664 return NTS_ERR_FAILED;
667 if(response_code != 201) {
668 char *message = docker_parse_json_message(response);
669 log_error("docker_container_create failed (%d): %s\n", response_code, message);
672 return NTS_ERR_FAILED;
675 cJSON *json_response = cJSON_Parse(response);
677 const cJSON *container_id = 0;
679 container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
681 if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) {
682 char container_id_short[13];
683 memset(container_id_short, '\0', sizeof(container_id_short));
684 strncpy(container_id_short, container_id->valuestring, 12);
686 container->id = strdup(container_id_short);
688 cJSON_Delete(json_response);
692 cJSON_Delete(json_response);
693 return NTS_ERR_FAILED;
698 static int docker_container_start(docker_container_t *container) {
702 sprintf(url, "http://v%s/containers/%s/start", framework_environment.settings.docker_engine_version, container->id);
705 int response_code = 0;
706 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", "", &response_code, &response);
707 if(rc != NTS_ERR_OK) {
708 log_error("http_socket_request failed\n");
709 return NTS_ERR_FAILED;
712 if(response_code == 304) {
713 log_error("docker_container_start failed (%d): container already started\n", response_code);
715 return NTS_ERR_FAILED;
717 else if(response_code != 204) {
718 char *message = docker_parse_json_message(response);
719 log_error("docker_container_start failed (%d): %s\n", response_code, message);
722 return NTS_ERR_FAILED;
730 static int docker_container_inspect(docker_container_t *container) {
734 sprintf(url, "http://v%s/containers/%s/json", framework_environment.settings.docker_engine_version, container->id);
737 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
738 if(rc != NTS_ERR_OK) {
739 log_error("http_socket_request failed\n");
741 return NTS_ERR_FAILED;
744 cJSON *json_response = cJSON_Parse(response);
746 if(json_response == 0) {
747 log_error("cJSON_Parse failed\n");
748 return NTS_ERR_FAILED;
752 cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings");
754 log_error("json parsing failed\n");
755 cJSON_Delete(json_response);
756 return NTS_ERR_FAILED;
759 cJSON *node = cJSON_GetObjectItem(main_node, "Networks");
761 log_error("json parsing failed\n");
762 cJSON_Delete(json_response);
763 return NTS_ERR_FAILED;
766 node = node->child; //get info from the first in array
768 log_error("json parsing failed\n");
769 cJSON_Delete(json_response);
770 return NTS_ERR_FAILED;
774 if(framework_environment.settings.ip_v6_enabled) {
775 element = cJSON_GetObjectItem(node, "GlobalIPv6Address");
778 element = cJSON_GetObjectItem(node, "IPAddress");
782 log_error("json parsing failed\n");
783 cJSON_Delete(json_response);
784 return NTS_ERR_FAILED;
787 container->docker_ip = strdup(element->valuestring);
789 cJSON_Delete(json_response);