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 static char *docker_parse_json_message(const char *json_string) {
364 cJSON *json_response = cJSON_Parse(json_string);
365 if(json_response == 0) {
366 log_error("cJSON_Parse failed\n");
370 cJSON *message = cJSON_GetObjectItem(json_response, "message");
372 log_error("json parsing failed\n");
373 cJSON_Delete(json_response);
377 char *ret = strdup(message->valuestring);
378 cJSON_Delete(json_response);
382 static int docker_add_port(cJSON *portBindings, uint16_t docker_port, uint16_t host_port) {
383 assert(portBindings);
385 cJSON *port = cJSON_CreateArray();
387 log_error("could not create JSON object: port\n");
388 return NTS_ERR_FAILED;
391 char dockerContainerPort[20];
392 sprintf(dockerContainerPort, "%d/tcp", docker_port);
394 if(cJSON_AddItemToObject(portBindings, dockerContainerPort, port) == 0) {
395 log_error("cJSON_AddItemToObject failed\n");
396 return NTS_ERR_FAILED;
399 cJSON *hostPort = cJSON_CreateObject();
401 log_error("could not create JSON object: HostPort\n");
402 return NTS_ERR_FAILED;
405 char dockerHostPort[20];
406 sprintf(dockerHostPort, "%d", host_port);
407 if(cJSON_AddStringToObject(hostPort, "HostPort", dockerHostPort) == 0) {
408 log_error("could not create JSON object: HostPortString\n");
409 cJSON_Delete(hostPort);
410 return NTS_ERR_FAILED;
413 if(cJSON_AddStringToObject(hostPort, "HostIp", "0.0.0.0") == 0) { //or, future, bind to container->host_ip
414 log_error("could not create JSON object: HostIpString\n");
415 cJSON_Delete(hostPort);
416 return NTS_ERR_FAILED;
419 if(cJSON_AddItemToArray(port, hostPort) == 0) {
420 log_error("cJSON_AddItemToArray failed\n");
421 cJSON_Delete(hostPort);
422 return NTS_ERR_FAILED;
428 static int docker_populate_images(docker_context_t *context, int count, const char *min_version) {
434 sprintf(url, "http://v%s/images/json", framework_environment.settings.docker_engine_version);
437 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
438 if(rc != NTS_ERR_OK) {
439 log_error("http_socket_request failed\n");
440 return NTS_ERR_FAILED;
443 cJSON *json_response = cJSON_Parse(response);
445 if(json_response == 0) {
446 log_error("cJSON_Parse failed\n");
447 return NTS_ERR_FAILED;
451 cJSON_ArrayForEach(element, json_response) {
452 cJSON *tag = cJSON_GetObjectItem(element, "RepoTags");
455 cJSON_ArrayForEach(ctag, tag) {
456 char *tag_name = ctag->valuestring; //contains repo/image:tag
457 for(int i = 0; i < count; i++) {
458 char *s = strstr(tag_name, context[i].image);
460 char *tag = s + strlen(context[i].image);
462 tag = strdup(s + strlen(context[i].image) + 1);
471 if(nts_vercmp(tag, min_version) >= 0) {
474 repo = strdup(tag_name);
475 *(strstr(repo, context[i].image) - 1) = 0;
481 context[i].available_images = (docker_available_images_t *)realloc(context[i].available_images, (sizeof(docker_available_images_t) * (context[i].available_images_count + 1)));
482 context[i].available_images[context[i].available_images_count].repo = repo;
483 context[i].available_images[context[i].available_images_count].tag = tag;
484 context[i].available_images_count++;
495 cJSON_Delete(json_response);
500 static int docker_container_create(const char *image, docker_container_t *container) {
504 cJSON *postDataJson = cJSON_CreateObject();
505 if(cJSON_AddStringToObject(postDataJson, "Image", image) == 0) {
506 log_error("could not create JSON object: Image\n");
507 return NTS_ERR_FAILED;
510 if(cJSON_AddStringToObject(postDataJson, "Hostname", container->name) == 0) {
511 log_error("could not create JSON object: Hostname\n");
512 cJSON_Delete(postDataJson);
513 return NTS_ERR_FAILED;
516 cJSON *hostConfig = cJSON_CreateObject();
517 if(hostConfig == 0) {
518 log_error("could not create JSON object: HostConfig\n");
519 cJSON_Delete(postDataJson);
520 return NTS_ERR_FAILED;
522 if(cJSON_AddItemToObject(postDataJson, "HostConfig", hostConfig) == 0) {
523 log_error("cJSON_AddItemToObject failed\n");
524 cJSON_Delete(postDataJson);
525 return NTS_ERR_FAILED;
528 cJSON *portBindings = cJSON_CreateObject();
529 if(portBindings == 0) {
530 printf("could not create JSON object: PortBindings");
531 cJSON_Delete(postDataJson);
532 return NTS_ERR_FAILED;
534 if(cJSON_AddItemToObject(hostConfig, "PortBindings", portBindings) == 0) {
535 log_error("cJSON_AddItemToObject failed\n");
536 cJSON_Delete(postDataJson);
537 return NTS_ERR_FAILED;
540 for(int i = 0; i < framework_environment.settings.ssh_connections; i++) {
541 if(docker_add_port(portBindings, container->docker_netconf_ssh_port + i, container->host_netconf_ssh_port + i) != NTS_ERR_OK) {
542 log_error("docker_add_port() failed\n");
543 cJSON_Delete(postDataJson);
544 return NTS_ERR_FAILED;
548 for(int i = 0; i < framework_environment.settings.tls_connections; i++) {
549 if(docker_add_port(portBindings, container->docker_netconf_tls_port + i, container->host_netconf_tls_port + i) != NTS_ERR_OK) {
550 log_error("docker_add_port() failed\n");
551 cJSON_Delete(postDataJson);
552 return NTS_ERR_FAILED;
556 for(int i = 0; i < framework_environment.settings.ftp_connections; i++) {
557 if(docker_add_port(portBindings, container->docker_ftp_port + i, container->host_ftp_port + i) != NTS_ERR_OK) {
558 log_error("docker_add_port() failed\n");
559 cJSON_Delete(postDataJson);
560 return NTS_ERR_FAILED;
564 for(int i = 0; i < framework_environment.settings.sftp_connections; i++) {
565 if(docker_add_port(portBindings, container->docker_sftp_port + i, container->host_sftp_port + i) != NTS_ERR_OK) {
566 log_error("docker_add_port() failed\n");
567 cJSON_Delete(postDataJson);
568 return NTS_ERR_FAILED;
572 //environment vars start
573 asprintf(&docker_environment_var[4].value, "%d", container->host_netconf_ssh_port);
574 asprintf(&docker_environment_var[5].value, "%d", container->host_netconf_tls_port);
575 asprintf(&docker_environment_var[6].value, "%d", container->host_ftp_port);
576 asprintf(&docker_environment_var[7].value, "%d", container->host_sftp_port);
578 cJSON *env_variables_array = cJSON_CreateArray();
579 if(env_variables_array == 0) {
580 log_error("Could not create JSON object: Env array\n");
581 cJSON_Delete(postDataJson);
582 free(docker_environment_var[4].value);
583 free(docker_environment_var[5].value);
584 free(docker_environment_var[6].value);
585 free(docker_environment_var[7].value);
586 return NTS_ERR_FAILED;
588 cJSON_AddItemToObject(postDataJson, "Env", env_variables_array);
590 for(int i = 0; i < docker_environment_var_count; i++) {
591 if(docker_environment_var[i].value) {
592 char *environment_var = 0;
593 asprintf(&environment_var, "%s=%s", docker_environment_var[i].name, docker_environment_var[i].value);
595 cJSON *env_var_obj = cJSON_CreateString(environment_var);
596 if(env_var_obj == 0) {
597 log_error("could not create JSON object\n");
598 cJSON_Delete(postDataJson);
599 free(docker_environment_var[4].value);
600 free(docker_environment_var[5].value);
601 free(docker_environment_var[6].value);
602 free(docker_environment_var[7].value);
603 free(environment_var);
604 return NTS_ERR_FAILED;
606 cJSON_AddItemToArray(env_variables_array, env_var_obj);
608 free(environment_var);
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 //environment vars finished
618 cJSON *netMode = cJSON_Duplicate(docker_network_info, 1);
619 cJSON_AddItemToObject(hostConfig, "NetworkMode", netMode);
621 char *post_data_string = 0;
622 post_data_string = cJSON_PrintUnformatted(postDataJson);
623 cJSON_Delete(postDataJson);
626 sprintf(url, "http:/v%s/containers/create?name=%s", framework_environment.settings.docker_engine_version, container->name);
629 int response_code = 0;
630 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", post_data_string, &response_code, &response);
631 free(post_data_string);
632 if(rc != NTS_ERR_OK) {
633 log_error("http_socket_request failed\n");
634 return NTS_ERR_FAILED;
637 if(response_code != 201) {
638 char *message = docker_parse_json_message(response);
639 log_error("docker_container_create failed (%d): %s\n", response_code, message);
642 return NTS_ERR_FAILED;
645 cJSON *json_response = cJSON_Parse(response);
647 const cJSON *container_id = 0;
649 container_id = cJSON_GetObjectItemCaseSensitive(json_response, "Id");
651 if(cJSON_IsString(container_id) && (container_id->valuestring != 0)) {
652 char container_id_short[13];
653 memset(container_id_short, '\0', sizeof(container_id_short));
654 strncpy(container_id_short, container_id->valuestring, 12);
656 container->id = strdup(container_id_short);
658 cJSON_Delete(json_response);
662 cJSON_Delete(json_response);
663 return NTS_ERR_FAILED;
668 static int docker_container_start(docker_container_t *container) {
672 sprintf(url, "http://v%s/containers/%s/start", framework_environment.settings.docker_engine_version, container->id);
675 int response_code = 0;
676 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "POST", "", &response_code, &response);
677 if(rc != NTS_ERR_OK) {
678 log_error("http_socket_request failed\n");
679 return NTS_ERR_FAILED;
682 if(response_code == 304) {
683 log_error("docker_container_start failed (%d): container already started\n", response_code);
685 return NTS_ERR_FAILED;
687 else if(response_code != 204) {
688 char *message = docker_parse_json_message(response);
689 log_error("docker_container_start failed (%d): %s\n", response_code, message);
692 return NTS_ERR_FAILED;
700 static int docker_container_inspect(docker_container_t *container) {
704 sprintf(url, "http://v%s/containers/%s/json", framework_environment.settings.docker_engine_version, container->id);
707 int rc = http_socket_request(url, DOCKER_SOCK_FNAME, "GET", "", 0, &response);
708 if(rc != NTS_ERR_OK) {
709 log_error("http_socket_request failed\n");
711 return NTS_ERR_FAILED;
714 cJSON *json_response = cJSON_Parse(response);
716 if(json_response == 0) {
717 log_error("cJSON_Parse failed\n");
718 return NTS_ERR_FAILED;
722 cJSON *main_node = cJSON_GetObjectItem(json_response, "NetworkSettings");
724 log_error("json parsing failed\n");
725 cJSON_Delete(json_response);
726 return NTS_ERR_FAILED;
729 cJSON *node = cJSON_GetObjectItem(main_node, "Networks");
731 log_error("json parsing failed\n");
732 cJSON_Delete(json_response);
733 return NTS_ERR_FAILED;
736 node = node->child; //get info from the first in array
738 log_error("json parsing failed\n");
739 cJSON_Delete(json_response);
740 return NTS_ERR_FAILED;
744 if(framework_environment.settings.ip_v6_enabled) {
745 element = cJSON_GetObjectItem(node, "GlobalIPv6Address");
748 element = cJSON_GetObjectItem(node, "IPAddress");
752 log_error("json parsing failed\n");
753 cJSON_Delete(json_response);
754 return NTS_ERR_FAILED;
757 container->docker_ip = strdup(element->valuestring);
759 cJSON_Delete(json_response);