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_AddStringToObject(hostPort, "HostIp", "0.0.0.0") == 0) { //or, future, bind to container->host_ip
450 log_error("could not create JSON object: HostIpString\n");
451 cJSON_Delete(hostPort);
452 return NTS_ERR_FAILED;
455 if(cJSON_AddItemToArray(port, hostPort) == 0) {
456 log_error("cJSON_AddItemToArray failed\n");
457 cJSON_Delete(hostPort);
458 return NTS_ERR_FAILED;
464 static int docker_populate_images(docker_context_t *context, int count, const char *min_version) {
470 sprintf(url, "http://v%s/images/json", framework_environment.settings.docker_engine_version);
473 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
474 if(rc != NTS_ERR_OK) {
475 log_error("http_socket_request failed\n");
476 return NTS_ERR_FAILED;
479 cJSON *json_response = cJSON_Parse(response);
481 if(json_response == 0) {
482 log_error("cJSON_Parse failed\n");
483 return NTS_ERR_FAILED;
487 cJSON_ArrayForEach(element, json_response) {
488 cJSON *tag = cJSON_GetObjectItem(element, "RepoTags");
491 cJSON_ArrayForEach(ctag, tag) {
492 char *tag_name = ctag->valuestring; //contains repo/image:tag
493 for(int i = 0; i < count; i++) {
494 char *s = strstr(tag_name, context[i].image);
496 char *tag = s + strlen(context[i].image);
498 tag = strdup(s + strlen(context[i].image) + 1);
507 if(nts_vercmp(tag, min_version) >= 0) {
510 repo = strdup(tag_name);
511 *(strstr(repo, context[i].image) - 1) = 0;
517 context[i].available_images = (docker_available_images_t *)realloc(context[i].available_images, (sizeof(docker_available_images_t) * (context[i].available_images_count + 1)));
518 context[i].available_images[context[i].available_images_count].repo = repo;
519 context[i].available_images[context[i].available_images_count].tag = tag;
520 context[i].available_images_count++;
531 cJSON_Delete(json_response);
536 static int docker_container_create(const char *image, docker_container_t *container) {
540 cJSON *postDataJson = cJSON_CreateObject();
541 if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) {
542 log_error("could not create JSON object: Image\n");
543 return NTS_ERR_FAILED;
546 if(cJSON_AddStringToObject(postDataJson, "Hostname", container->name) == 0) {
547 log_error("could not create JSON object: Hostname\n");
548 cJSON_Delete(postDataJson);
549 return NTS_ERR_FAILED;
552 cJSON *hostConfig = cJSON_CreateObject();
553 if(hostConfig == 0) {
554 log_error("could not create JSON object: HostConfig\n");
555 cJSON_Delete(postDataJson);
556 return NTS_ERR_FAILED;
558 if(cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig) == 0) {
559 log_error("cJSON_AddItemToObject failed\n");
560 cJSON_Delete(postDataJson);
561 return NTS_ERR_FAILED;
564 cJSON *portBindings = cJSON_CreateObject();
565 if(portBindings == 0) {
566 printf("could not create JSON object: PortBindings");
567 cJSON_Delete(postDataJson);
568 return NTS_ERR_FAILED;
570 if(cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings) == 0) {
571 log_error("cJSON_AddItemToObject failed\n");
572 cJSON_Delete(postDataJson);
573 return NTS_ERR_FAILED;
576 for(int i = 0; i < framework_environment.settings.ssh_connections; i++) {
577 if(docker_add_port(portBindings, container->docker_netconf_ssh_port + i, container->host_netconf_ssh_port + i) != NTS_ERR_OK) {
578 log_error("docker_add_port() failed\n");
579 cJSON_Delete(postDataJson);
580 return NTS_ERR_FAILED;
584 for(int i = 0; i < framework_environment.settings.tls_connections; i++) {
585 if(docker_add_port(portBindings, container->docker_netconf_tls_port + i, container->host_netconf_tls_port + i) != NTS_ERR_OK) {
586 log_error("docker_add_port() failed\n");
587 cJSON_Delete(postDataJson);
588 return NTS_ERR_FAILED;
592 for(int i = 0; i < framework_environment.settings.ftp_connections; i++) {
593 if(docker_add_port(portBindings, container->docker_ftp_port + i, container->host_ftp_port + i) != NTS_ERR_OK) {
594 log_error("docker_add_port() failed\n");
595 cJSON_Delete(postDataJson);
596 return NTS_ERR_FAILED;
600 for(int i = 0; i < framework_environment.settings.sftp_connections; i++) {
601 if(docker_add_port(portBindings, container->docker_sftp_port + i, container->host_sftp_port + i) != NTS_ERR_OK) {
602 log_error("docker_add_port() failed\n");
603 cJSON_Delete(postDataJson);
604 return NTS_ERR_FAILED;
608 //environment vars start
609 asprintf(&docker_environment_var[4].value, "%d", container->host_netconf_ssh_port);
610 asprintf(&docker_environment_var[5].value, "%d", container->host_netconf_tls_port);
611 asprintf(&docker_environment_var[6].value, "%d", container->host_ftp_port);
612 asprintf(&docker_environment_var[7].value, "%d", container->host_sftp_port);
614 cJSON *env_variables_array = cJSON_CreateArray();
615 if(env_variables_array == 0) {
616 log_error("Could not create JSON object: Env array\n");
617 cJSON_Delete(postDataJson);
618 free(docker_environment_var[4].value);
619 free(docker_environment_var[5].value);
620 free(docker_environment_var[6].value);
621 free(docker_environment_var[7].value);
622 return NTS_ERR_FAILED;
624 cJSON_AddItemToObject(postDataJson, "Env", env_variables_array);
626 for(int i = 0; i < docker_environment_var_count; i++) {
627 if(docker_environment_var[i].value) {
628 char *environment_var = 0;
629 asprintf(&environment_var, "%s=%s", docker_environment_var[i].name, docker_environment_var[i].value);
631 cJSON *env_var_obj = cJSON_CreateString(environment_var);
632 if(env_var_obj == 0) {
633 log_error("could not create JSON object\n");
634 cJSON_Delete(postDataJson);
635 free(docker_environment_var[4].value);
636 free(docker_environment_var[5].value);
637 free(docker_environment_var[6].value);
638 free(docker_environment_var[7].value);
639 free(environment_var);
640 return NTS_ERR_FAILED;
642 cJSON_AddItemToArray(env_variables_array, env_var_obj);
644 free(environment_var);
648 free(docker_environment_var[4].value);
649 free(docker_environment_var[5].value);
650 free(docker_environment_var[6].value);
651 free(docker_environment_var[7].value);
652 //environment vars finished
654 cJSON *netMode = cJSON_Duplicate(docker_network_info, 1);
655 cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
657 char *post_data_string = 0;
658 post_data_string = cJSON_PrintUnformatted(postDataJson);
659 cJSON_Delete(postDataJson);
662 sprintf(url, "http:/v%s/containers/create?name=%s", framework_environment.settings.docker_engine_version, container->name);
665 int response_code = 0;
666 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response);
667 free(post_data_string);
668 if(rc != NTS_ERR_OK) {
669 log_error("http_socket_request failed\n");
670 return NTS_ERR_FAILED;
673 if(response_code != 201) {
674 char *message = docker_parse_json_message(response);
675 log_error("docker_container_create failed (%d): %s\n", response_code, message);
678 return NTS_ERR_FAILED;
681 cJSON *json_response = cJSON_Parse(response);
683 const cJSON *container_id = 0;
685 container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
687 if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) {
688 char container_id_short[13];
689 memset(container_id_short, '\0', sizeof(container_id_short));
690 strncpy(container_id_short, container_id->valuestring, 12);
692 container->id = strdup(container_id_short);
694 cJSON_Delete(json_response);
698 cJSON_Delete(json_response);
699 return NTS_ERR_FAILED;
704 static int docker_container_start(docker_container_t *container) {
708 sprintf(url, "http://v%s/containers/%s/start", framework_environment.settings.docker_engine_version, container->id);
711 int response_code = 0;
712 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", "", &response_code, &response);
713 if(rc != NTS_ERR_OK) {
714 log_error("http_socket_request failed\n");
715 return NTS_ERR_FAILED;
718 if(response_code == 304) {
719 log_error("docker_container_start failed (%d): container already started\n", response_code);
721 return NTS_ERR_FAILED;
723 else if(response_code != 204) {
724 char *message = docker_parse_json_message(response);
725 log_error("docker_container_start failed (%d): %s\n", response_code, message);
728 return NTS_ERR_FAILED;
736 static int docker_container_inspect(docker_container_t *container) {
740 sprintf(url, "http://v%s/containers/%s/json", framework_environment.settings.docker_engine_version, container->id);
743 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
744 if(rc != NTS_ERR_OK) {
745 log_error("http_socket_request failed\n");
747 return NTS_ERR_FAILED;
750 cJSON *json_response = cJSON_Parse(response);
752 if(json_response == 0) {
753 log_error("cJSON_Parse failed\n");
754 return NTS_ERR_FAILED;
758 cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings");
760 log_error("json parsing failed\n");
761 cJSON_Delete(json_response);
762 return NTS_ERR_FAILED;
765 cJSON *node = cJSON_GetObjectItem(main_node, "Networks");
767 log_error("json parsing failed\n");
768 cJSON_Delete(json_response);
769 return NTS_ERR_FAILED;
772 node = node->child; //get info from the first in array
774 log_error("json parsing failed\n");
775 cJSON_Delete(json_response);
776 return NTS_ERR_FAILED;
780 if(framework_environment.settings.ip_v6_enabled) {
781 element = cJSON_GetObjectItem(node, "GlobalIPv6Address");
784 element = cJSON_GetObjectItem(node, "IPAddress");
788 log_error("json parsing failed\n");
789 cJSON_Delete(json_response);
790 return NTS_ERR_FAILED;
793 container->docker_ip = strdup(element->valuestring);
795 cJSON_Delete(json_response);