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 container->docker_ftp_port= STANDARD_FTP_PORT;
195 container->docker_sftp_port= STANDARD_SFTP_PORT;
197 container->host_ip = strdup(framework_environment.host.ip);
198 container->host_netconf_ssh_port = host_netconf_ssh_port;
199 container->host_netconf_tls_port = host_netconf_tls_port;
200 container->host_ftp_port = host_ftp_port;
201 container->host_sftp_port = host_sftp_port;
203 int rc = docker_container_create(image_full, container);
204 if(rc != NTS_ERR_OK) {
205 log_error("docker_container_create failed\n");
206 return NTS_ERR_FAILED;
209 rc = docker_container_start(container);
210 if(rc != NTS_ERR_OK) {
211 log_error("docker_container_start failed\n");
212 docker_stop(container);
213 return NTS_ERR_FAILED;
216 rc = docker_container_inspect(container);
217 if(rc != NTS_ERR_OK) {
218 log_error("docker_container_inspect failed\n");
219 docker_stop(container);
220 return NTS_ERR_FAILED;
223 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);
228 int docker_stop(docker_container_t *container) {
232 sprintf(url, "http://v%s/containers/%s?force=true", framework_environment.settings.docker_engine_version, container->id);
234 free(container->name);
236 free(container->docker_ip);
237 free(container->host_ip);
239 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "DELETE", "", 0, 0);
240 if(rc != NTS_ERR_OK) {
241 log_error("http_socket_request failed\n");
242 return NTS_ERR_FAILED;
248 int docker_usage_get(const char **instances_id, int count, docker_usage_t *usage) {
249 assert(instances_id);
256 char full_text[1024 * 1024];
257 FILE* pipe = popen("docker stats --no-stream --format \"table {{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|\"", "r");
259 log_error("popen() failed\n");
260 return NTS_ERR_FAILED;
266 n = fread(buffer, 1, sizeof(buffer), pipe);
267 for(int i = 0; i < n; i++) {
268 full_text[k++] = buffer[i];
281 char *d = strstr(c + 1, "\n");
283 for(char *i = c + 1; i < d; i++) {
284 line[i - c - 1] = *i;
288 char container_name[1024];
293 char *x = strstr(line, "|");
294 for(char *i = line; i < x; i++) {
295 container_name[i - line] = *i;
296 container_name[i - line + 1] = 0;
300 x = strstr(start, "|");
301 for(char *i = start; i < x; i++) {
302 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
303 buff[i - start] = *i;
311 cpu = strtof(buff, 0);
315 x = strstr(start, "|");
316 for(char *i = start; i < x; i++) {
317 if(((*i >= '0') && (*i <= '9')) || (*i == '.')) {
318 buff[i - start] = *i;
329 mem = strtof(buff, 0) * mul;
332 if(strcmp(container_name, framework_environment.settings.hostname) == 0) {
337 for(int i = 0; i < count; i++) {
338 if(strcmp(container_name, instances_id[i]) == 0) {
350 usage->cpu /= get_nprocs();
355 static char *docker_parse_json_message(const char *json_string) {
358 cJSON *json_response = cJSON_Parse(json_string);
359 if(json_response == 0) {
360 log_error("cJSON_Parse failed\n");
364 cJSON *message = cJSON_GetObjectItem(json_response, "message");
366 log_error("json parsing failed\n");
367 cJSON_Delete(json_response);
371 char *ret = strdup(message->valuestring);
372 cJSON_Delete(json_response);
376 static int docker_add_port(cJSON *portBindings, uint16_t docker_port, uint16_t host_port) {
377 assert(portBindings);
379 cJSON *port = cJSON_CreateArray();
381 log_error("could not create JSON object: port\n");
382 return NTS_ERR_FAILED;
385 char dockerContainerPort[20];
386 sprintf(dockerContainerPort, "%d/tcp", docker_port);
388 if(cJSON_AddItemToObject(portBindings, dockerContainerPort, port) == 0) {
389 log_error("cJSON_AddItemToObject failed\n");
390 return NTS_ERR_FAILED;
393 cJSON *hostPort = cJSON_CreateObject();
395 log_error("could not create JSON object: HostPort\n");
396 return NTS_ERR_FAILED;
399 char dockerHostPort[20];
400 sprintf(dockerHostPort, "%d", host_port);
401 if(cJSON_AddStringToObject(hostPort, "HostPort", dockerHostPort) == 0) {
402 log_error("could not create JSON object: HostPortString\n");
403 cJSON_Delete(hostPort);
404 return NTS_ERR_FAILED;
407 if(cJSON_AddStringToObject(hostPort, "HostIp", "0.0.0.0") == 0) { //or, future, bind to container->host_ip
408 log_error("could not create JSON object: HostIpString\n");
409 cJSON_Delete(hostPort);
410 return NTS_ERR_FAILED;
413 if(cJSON_AddItemToArray(port, hostPort) == 0) {
414 log_error("cJSON_AddItemToArray failed\n");
415 cJSON_Delete(hostPort);
416 return NTS_ERR_FAILED;
422 static int docker_populate_images(docker_context_t *context, int count, const char *min_version) {
428 sprintf(url, "http://v%s/images/json", framework_environment.settings.docker_engine_version);
431 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
432 if(rc != NTS_ERR_OK) {
433 log_error("http_socket_request failed\n");
434 return NTS_ERR_FAILED;
437 cJSON *json_response = cJSON_Parse(response);
439 if(json_response == 0) {
440 log_error("cJSON_Parse failed\n");
441 return NTS_ERR_FAILED;
445 cJSON_ArrayForEach(element, json_response) {
446 cJSON *tag = cJSON_GetObjectItem(element, "RepoTags");
449 cJSON_ArrayForEach(ctag, tag) {
450 char *tag_name = ctag->valuestring; //contains repo/image:tag
451 for(int i = 0; i < count; i++) {
452 char *s = strstr(tag_name, context[i].image);
454 char *tag = s + strlen(context[i].image);
456 tag = strdup(s + strlen(context[i].image) + 1);
465 if(nts_vercmp(tag, min_version) >= 0) {
468 repo = strdup(tag_name);
469 *(strstr(repo, context[i].image) - 1) = 0;
475 context[i].available_images = (docker_available_images_t *)realloc(context[i].available_images, (sizeof(docker_available_images_t) * (context[i].available_images_count + 1)));
476 context[i].available_images[context[i].available_images_count].repo = repo;
477 context[i].available_images[context[i].available_images_count].tag = tag;
478 context[i].available_images_count++;
489 cJSON_Delete(json_response);
494 static int docker_container_create(const char *image, docker_container_t *container) {
498 cJSON *postDataJson = cJSON_CreateObject();
499 if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) {
500 log_error("could not create JSON object: Image\n");
501 return NTS_ERR_FAILED;
504 if(cJSON_AddStringToObject(postDataJson, "Hostname", container->name) == 0) {
505 log_error("could not create JSON object: Hostname\n");
506 cJSON_Delete(postDataJson);
507 return NTS_ERR_FAILED;
510 cJSON *hostConfig = cJSON_CreateObject();
511 if(hostConfig == 0) {
512 log_error("could not create JSON object: HostConfig\n");
513 cJSON_Delete(postDataJson);
514 return NTS_ERR_FAILED;
516 if(cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig) == 0) {
517 log_error("cJSON_AddItemToObject failed\n");
518 cJSON_Delete(postDataJson);
519 return NTS_ERR_FAILED;
522 cJSON *portBindings = cJSON_CreateObject();
523 if(portBindings == 0) {
524 printf("could not create JSON object: PortBindings");
525 cJSON_Delete(postDataJson);
526 return NTS_ERR_FAILED;
528 if(cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings) == 0) {
529 log_error("cJSON_AddItemToObject failed\n");
530 cJSON_Delete(postDataJson);
531 return NTS_ERR_FAILED;
534 for(int i = 0; i < framework_environment.settings.ssh_connections; i++) {
535 if(docker_add_port(portBindings, container->docker_netconf_ssh_port + i, container->host_netconf_ssh_port + i) != NTS_ERR_OK) {
536 log_error("docker_add_port() failed\n");
537 cJSON_Delete(postDataJson);
538 return NTS_ERR_FAILED;
542 for(int i = 0; i < framework_environment.settings.tls_connections; i++) {
543 if(docker_add_port(portBindings, container->docker_netconf_tls_port + i, container->host_netconf_tls_port + i) != NTS_ERR_OK) {
544 log_error("docker_add_port() failed\n");
545 cJSON_Delete(postDataJson);
546 return NTS_ERR_FAILED;
550 for(int i = 0; i < framework_environment.settings.ftp_connections; i++) {
551 if(docker_add_port(portBindings, container->docker_ftp_port + i, container->host_ftp_port + i) != NTS_ERR_OK) {
552 log_error("docker_add_port() failed\n");
553 cJSON_Delete(postDataJson);
554 return NTS_ERR_FAILED;
558 for(int i = 0; i < framework_environment.settings.sftp_connections; i++) {
559 if(docker_add_port(portBindings, container->docker_sftp_port + i, container->host_sftp_port + i) != NTS_ERR_OK) {
560 log_error("docker_add_port() failed\n");
561 cJSON_Delete(postDataJson);
562 return NTS_ERR_FAILED;
566 //environment vars start
567 asprintf(&docker_environment_var[4].value, "%d", container->host_netconf_ssh_port);
568 asprintf(&docker_environment_var[5].value, "%d", container->host_netconf_tls_port);
569 asprintf(&docker_environment_var[6].value, "%d", container->host_ftp_port);
570 asprintf(&docker_environment_var[7].value, "%d", container->host_sftp_port);
572 cJSON *env_variables_array = cJSON_CreateArray();
573 if(env_variables_array == 0) {
574 log_error("Could not create JSON object: Env array\n");
575 cJSON_Delete(postDataJson);
576 free(docker_environment_var[4].value);
577 free(docker_environment_var[5].value);
578 free(docker_environment_var[6].value);
579 free(docker_environment_var[7].value);
580 return NTS_ERR_FAILED;
582 cJSON_AddItemToObject(postDataJson, "Env", env_variables_array);
584 for(int i = 0; i < docker_environment_var_count; i++) {
585 if(docker_environment_var[i].value) {
586 char *environment_var = 0;
587 asprintf(&environment_var, "%s=%s", docker_environment_var[i].name, docker_environment_var[i].value);
589 cJSON *env_var_obj = cJSON_CreateString(environment_var);
590 if(env_var_obj == 0) {
591 log_error("could not create JSON object\n");
592 cJSON_Delete(postDataJson);
593 free(docker_environment_var[4].value);
594 free(docker_environment_var[5].value);
595 free(docker_environment_var[6].value);
596 free(docker_environment_var[7].value);
597 free(environment_var);
598 return NTS_ERR_FAILED;
600 cJSON_AddItemToArray(env_variables_array, env_var_obj);
602 free(environment_var);
606 free(docker_environment_var[4].value);
607 free(docker_environment_var[5].value);
608 free(docker_environment_var[6].value);
609 free(docker_environment_var[7].value);
610 //environment vars finished
612 cJSON *netMode = cJSON_Duplicate(docker_network_info, 1);
613 cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
615 char *post_data_string = 0;
616 post_data_string = cJSON_PrintUnformatted(postDataJson);
617 cJSON_Delete(postDataJson);
620 sprintf(url, "http:/v%s/containers/create?name=%s", framework_environment.settings.docker_engine_version, container->name);
623 int response_code = 0;
624 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response);
625 free(post_data_string);
626 if(rc != NTS_ERR_OK) {
627 log_error("http_socket_request failed\n");
628 return NTS_ERR_FAILED;
631 if(response_code != 201) {
632 char *message = docker_parse_json_message(response);
633 log_error("docker_container_create failed (%d): %s\n", response_code, message);
636 return NTS_ERR_FAILED;
639 cJSON *json_response = cJSON_Parse(response);
641 const cJSON *container_id = 0;
643 container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
645 if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) {
646 char container_id_short[13];
647 memset(container_id_short, '\0', sizeof(container_id_short));
648 strncpy(container_id_short, container_id->valuestring, 12);
650 container->id = strdup(container_id_short);
652 cJSON_Delete(json_response);
656 cJSON_Delete(json_response);
657 return NTS_ERR_FAILED;
662 static int docker_container_start(docker_container_t *container) {
666 sprintf(url, "http://v%s/containers/%s/start", framework_environment.settings.docker_engine_version, container->id);
669 int response_code = 0;
670 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\n");
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\n", response_code, message);
686 return NTS_ERR_FAILED;
694 static int docker_container_inspect(docker_container_t *container) {
698 sprintf(url, "http://v%s/containers/%s/json", framework_environment.settings.docker_engine_version, container->id);
701 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
702 if(rc != NTS_ERR_OK) {
703 log_error("http_socket_request failed\n");
705 return NTS_ERR_FAILED;
708 cJSON *json_response = cJSON_Parse(response);
710 if(json_response == 0) {
711 log_error("cJSON_Parse failed\n");
712 return NTS_ERR_FAILED;
716 cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings");
718 log_error("json parsing failed\n");
719 cJSON_Delete(json_response);
720 return NTS_ERR_FAILED;
723 cJSON *node = cJSON_GetObjectItem(main_node, "Networks");
725 log_error("json parsing failed\n");
726 cJSON_Delete(json_response);
727 return NTS_ERR_FAILED;
730 node = node->child; //get info from the first in array
732 log_error("json parsing failed\n");
733 cJSON_Delete(json_response);
734 return NTS_ERR_FAILED;
738 if(framework_environment.settings.ip_v6_enabled) {
739 element = cJSON_GetObjectItem(node, "GlobalIPv6Address");
742 element = cJSON_GetObjectItem(node, "IPAddress");
746 log_error("json parsing failed\n");
747 cJSON_Delete(json_response);
748 return NTS_ERR_FAILED;
751 container->docker_ip = strdup(element->valuestring);
753 cJSON_Delete(json_response);