diff --git a/client/client_state.cpp b/client/client_state.cpp index f461b367177..aee937f6d5d 100644 --- a/client/client_state.cpp +++ b/client/client_state.cpp @@ -280,24 +280,15 @@ void CLIENT_STATE::show_host_info() { } #endif } - if (host_info.docker_use){ + if (host_info.docker_available) { msg_printf(NULL, MSG_INFO, "Docker is installed and available"); - } - else{ + } else { msg_printf(NULL, MSG_INFO, "Docker is not installed or is not available for running task"); } - - if (strlen(host_info.docker_compose_version)){ - if ((strstr(host_info.docker_compose_version, "v1")) && (strstr(host_info.docker_compose_version, "v2"))){ - msg_printf(NULL, MSG_INFO, "Docker compose (new and old versions: docker-compose and docker compose) is installed and available for running task"); - }else if (strstr(host_info.docker_compose_version, "v1")) { - msg_printf(NULL, MSG_INFO, "Docker compose (old version: docker-compose) is installed and available for running task"); - }else if (strstr(host_info.docker_compose_version, "v2")){ - msg_printf(NULL, MSG_INFO, "Docker compose (new version: docker compose) is installed and available for running task"); - } - else{ - msg_printf(NULL, MSG_INFO, "Docker compose is not installed or is not available for running task"); - } + if (host_info.docker_compose_available) { + msg_printf(NULL, MSG_INFO, "Docker compose is installed and available"); + } else { + msg_printf(NULL, MSG_INFO, "Docker compose is not installed or is not available for running task"); } } diff --git a/client/cs_statefile.cpp b/client/cs_statefile.cpp index e77044597a4..71c6075ccc1 100644 --- a/client/cs_statefile.cpp +++ b/client/cs_statefile.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2022 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -977,13 +977,6 @@ int CLIENT_STATE::parse_app_info(PROJECT* p, FILE* in) { delete avp; continue; } - if (cc_config.dont_use_docker_compose && strstr(avp->plan_class, "docker")) { - msg_printf(p, MSG_INFO, - "skipping app with docker compose in app_info.xml; docker compose disabled in cc_config.xml" - ); - delete avp; - continue; - } if (strlen(avp->platform) == 0) { safe_strcpy(avp->platform, get_primary_platform()); } diff --git a/client/hostinfo_unix.cpp b/client/hostinfo_unix.cpp index 32fd478b1f8..c7f5bad0d34 100644 --- a/client/hostinfo_unix.cpp +++ b/client/hostinfo_unix.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2021 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,7 +20,6 @@ // Try to keep this well-organized and not nested. #include "version.h" // version numbers from autoconf -#include #include "cpp.h" #include "config.h" @@ -1237,91 +1236,38 @@ int HOST_INFO::get_virtualbox_version() { //check if docker compose or docker-compose is installed on volunteer's host // int HOST_INFO::get_docker_compose_info(){ - FILE* fd; - char buf[MAXPATHLEN]; - - std::ofstream compose_file ("docker-compose.yaml"); - compose_file << "version: \"2\"\nservices: \n hello: \n image: \"hello-world\" \n" << std::endl; - - char* docker_command = "docker-compose up 2>&1"; - fd = popen(docker_command, "r"); - if (fd){ - while(!feof(fd)){ - if (fgets(buf, sizeof(buf), fd)){ - if (strstr(buf, "Hello from Docker!")){ - safe_strcat(docker_compose_version, "v1"); - break; - } - } - } - pclose(fd); - } - - docker_command = "docker compose up 2>&1"; - fd = popen(docker_command, "r"); - if (fd){ - while(!feof(fd)){ - if (fgets(buf, sizeof(buf), fd)){ - if (strstr(buf, "Hello from Docker!")){ - safe_strcat(docker_compose_version, "v2"); - break; - } - } - } - pclose(fd); - } - - std::remove("docker-compose.yaml"); - - if (!(strstr(docker_compose_version, "v1"))){ - if (!(strstr(docker_compose_version, "v2"))){ - safe_strcat(docker_compose_version, "not_used"); + FILE* f = popen(command_get_docker_compose_version, "r"); + if (f) { + char buf[256]; + fgets(buf, 256, f); + std::string version; + if (get_docker_compose_version_string(buf, version)) { + docker_compose_available = true; + safe_strcpy(docker_compose_version, version.c_str()); } + pclose(f); + return 0; } - - return 0; + return 1; } //check if docker is installed on volunteer's host // -int HOST_INFO::get_docker_info(bool& docker_use){ - char buf[256]; - char buf_command[256]; - FILE* fd; - FILE* fd_1; - char docker_cmd [256]; - - strcpy(docker_cmd, "which -a docker 2>&1"); - fd = popen(docker_cmd, "r"); - if (fd){ - while(!feof(fd)){ - if (fgets(buf, sizeof(buf), fd)){ - strip_whitespace(buf); - if (!(access(buf, X_OK))){ - strcpy(docker_cmd, buf); - strcat(docker_cmd, " run --rm hello-world 2>&1"); - fd_1 = popen(docker_cmd, "r"); - if (fd_1){ - while(!feof(fd_1)){ - if (fgets(buf_command, sizeof(buf_command), fd_1)){ - if (strstr(buf_command, "Hello from Docker!")){ - docker_use = true; - break; - } - } - } - pclose(fd_1); - } - } - if (docker_use){ - break; - } - } +int HOST_INFO::get_docker_info(){ + FILE* f = popen(command_get_docker_version, "r"); + if (f) { + char buf[256]; + fgets(buf, 256, f); + std::string version; + if (get_docker_version_string(buf, version)) { + docker_available = true; + safe_strcpy(docker_version, version.c_str()); } - pclose(fd); + pclose(f); + return 0; } - return 0; + return 1; } @@ -1773,10 +1719,7 @@ int HOST_INFO::get_host_info(bool init) { } if(!cc_config.dont_use_docker){ - get_docker_info(docker_use); - } - - if(!cc_config.dont_use_docker_compose){ + get_docker_info(); get_docker_compose_info(); } diff --git a/client/hostinfo_win.cpp b/client/hostinfo_win.cpp index e7fcc7822a0..95a83d424e3 100644 --- a/client/hostinfo_win.cpp +++ b/client/hostinfo_win.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2018 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -29,8 +29,6 @@ #include "str_util.h" #include "str_replace.h" #include "util.h" -#include - #include "client_msgs.h" #include "client_types.h" @@ -1552,98 +1550,6 @@ int get_network_usage_totals(unsigned int& total_received, unsigned int& total_s return iRetVal; } -//check if docker compose or docker-compose is installed on volunteer's host -// -int HOST_INFO::get_docker_compose_info(){ - FILE* fd; - char buf[MAXPATHLEN]; - - std::ofstream compose_file ("docker-compose.yaml"); - compose_file << "version: \"2\"\nservices: \n hello: \n image: \"hello-world\" \n" << std::endl; - - char* docker_command = "wsl docker-compose up 2>&1"; - fd = _popen(docker_command, "r"); - if (fd){ - while(!feof(fd)){ - if (fgets(buf, sizeof(buf), fd)){ - if (strstr(buf, "Hello from Docker!")){ - safe_strcat(docker_compose_version, "v1"); - break; - } - } - } - _pclose(fd); - } - - docker_command = "wsl docker compose up 2>&1"; - fd = _popen(docker_command, "r"); - if (fd){ - while(!feof(fd)){ - if (fgets(buf, sizeof(buf), fd)){ - if (strstr(buf, "Hello from Docker!")){ - safe_strcat(docker_compose_version, "v2"); - break; - } - } - } - _pclose(fd); - } - - std::remove("docker-compose.yaml"); - - if (!(strstr(docker_compose_version, "v1"))){ - if (!(strstr(docker_compose_version, "v2"))){ - safe_strcat(docker_compose_version, "not_used"); - } - } - - return 0; -} - - -//check if docker is installed on volunteer's host -// -int HOST_INFO::get_docker_info(bool& docker_use){ - char buf[256]; - FILE* fd; - FILE *fd_1; - - char* docker_command = "wsl which -a docker 2>&1"; - fd = _popen(docker_command, "r"); - if (fd) { - while (!feof(fd)){ - if (fgets(buf + 4, sizeof(buf), fd)){ - buf[0] = 'w'; - buf[1] = 's'; - buf[2] = 'l'; - buf[3] = ' '; - int i, j; - for (i = 0, j = 0; buf[i]; i++) { - if (buf[i] != '\n') { - buf[j++] = buf[i]; - } - } - buf[j] = '\0'; - docker_command = strcat(buf, " run --rm hello-world 2>&1"); - fd_1 = _popen(docker_command, "r"); - if (fd_1){ - while (!feof(fd_1)){ - if (fgets(buf, sizeof(buf), fd_1)){ - if (strstr(buf, "Hello from Docker!")){ - docker_use = true; - break; - } - } - } - _pclose(fd_1); - } - } - } - _pclose(fd); - } - return 0; -} - // see if Virtualbox is installed // int HOST_INFO::get_virtualbox_version() { @@ -1763,26 +1669,9 @@ int HOST_INFO::get_host_info(bool init) { if (!cc_config.dont_use_wsl) { OSVERSIONINFOEX osvi; if (get_OSVERSIONINFO(osvi) && osvi.dwMajorVersion >= 10) { - get_wsl_information(wsl_available, wsls); + get_wsl_information(wsl_available, wsls, !cc_config.dont_use_docker, docker_available, docker_compose_available); } } - - if ((!cc_config.dont_use_docker) && (!cc_config.dont_use_wsl)){ - if (wsl_available){ - for (size_t i = 0; i < wsls.wsls.size(); ++i){ - const WSL& wsl = wsls.wsls[i]; - if (wsl.is_default){ - if (wsl.version.find("WSL2") != std::string::npos){ - get_docker_info(docker_use); - if (!cc_config.dont_use_docker_compose){ - get_docker_compose_info(); - } - } - } - } - } - } - #endif if (!cc_config.dont_use_vbox) { get_virtualbox_version(); diff --git a/client/hostinfo_wsl.cpp b/client/hostinfo_wsl.cpp index c6b356efa19..9c80b62f33e 100644 --- a/client/hostinfo_wsl.cpp +++ b/client/hostinfo_wsl.cpp @@ -98,15 +98,74 @@ bool get_available_wsls(std::vector>& wsls, std::s typedef HRESULT(WINAPI *PWslLaunch)(PCWSTR, PCWSTR, BOOL, HANDLE, HANDLE, HANDLE, HANDLE*); -HINSTANCE wsl_lib = NULL; +struct resource_helper { +public: + HINSTANCE wsl_lib = NULL; + HANDLE in_read = NULL; + HANDLE in_write = NULL; + HANDLE out_read = NULL; + HANDLE out_write = NULL; + PWslLaunch pWslLaunch = NULL; + + ~resource_helper() { + close_handle(in_read); + close_handle(in_write); + close_handle(out_read); + close_handle(out_write); + + if (wsl_lib) { + FreeLibrary(wsl_lib); + } + } + + // prepare resources + int prepare_resources() { + wsl_lib = NULL; + in_read = NULL; + in_write = NULL; + out_read = NULL; + out_write = NULL; + pWslLaunch = NULL; + + wsl_lib = LoadLibrary("wslapi.dll"); + if (!wsl_lib) { + return 1; + } + + pWslLaunch = (PWslLaunch)GetProcAddress(wsl_lib, "WslLaunch"); -HANDLE in_read = NULL; -HANDLE in_write = NULL; -HANDLE out_read = NULL; -HANDLE out_write = NULL; + if (!pWslLaunch) { + return 1; + } + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&out_read, &out_write, &sa, 0)) { + return 1; + } + if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { + return 1; + } + if (!CreatePipe(&in_read, &in_write, &sa, 0)) { + return 1; + } + if (!SetHandleInformation(in_write, HANDLE_FLAG_INHERIT, 0)) { + return 1; + } -PWslLaunch pWslLaunch = NULL; + return 0; + } +private: + inline void close_handle(HANDLE handle) { + if (handle) { + CloseHandle(handle); + } + } +}; //convert std::string to PCWSTR //taken from https://stackoverflow.com/questions/27220/how-to-convert-stdstring-to-lpcwstr-in-c-unicode @@ -121,11 +180,11 @@ std::wstring s2ws(const std::string& s) return r; } -bool create_wsl_process(const std::string& wsl_distro_name, const std::string& command, HANDLE* handle, bool use_current_work_dir = false) { - return (pWslLaunch(s2ws(wsl_distro_name).c_str(), s2ws(command).c_str(), use_current_work_dir, in_read, out_write, out_write, handle) == S_OK); +bool create_wsl_process(const resource_helper& rs, const std::string& wsl_distro_name, const std::string& command, HANDLE* handle, bool use_current_work_dir = false) { + return (rs.pWslLaunch(s2ws(wsl_distro_name).c_str(), s2ws(command).c_str(), use_current_work_dir, rs.in_read, rs.out_write, rs.out_write, handle) == S_OK); } -bool CreateWslProcess(const std::string& wsl_app, const std::string& command, HANDLE& handle) { +bool CreateWslProcess(const HANDLE& out_write, const std::string& wsl_app, const std::string& command, HANDLE& handle) { PROCESS_INFORMATION pi; STARTUPINFO si; @@ -152,26 +211,7 @@ bool CreateWslProcess(const std::string& wsl_app, const std::string& command, HA return res; } -inline void close_handle(HANDLE handle) { - if (handle) { - CloseHandle(handle); - } -} - -int free_resources_and_exit(const int return_code) { - close_handle(in_read); - close_handle(in_write); - close_handle(out_read); - close_handle(out_write); - - if (wsl_lib) { - FreeLibrary(wsl_lib); - } - - return return_code; -} - -std::string read_from_pipe(HANDLE handle) { +std::string read_from_pipe(const HANDLE& handle, const HANDLE& out_read) { DWORD avail, read, exitcode; const int bufsize = 256; char buf[bufsize]; @@ -222,14 +262,7 @@ void parse_sysctl_output(const std::vector& lines, std::string& ost // Returns the OS name and version for WSL when enabled // -int get_wsl_information(bool& wsl_available, WSLS& wsls) { - wsl_lib = NULL; - in_read = NULL; - in_write = NULL; - out_read = NULL; - out_write = NULL; - pWslLaunch = NULL; - +int get_wsl_information(bool& wsl_available, WSLS& wsls, bool detect_docker, bool& docker_available, bool& docker_compose_available) { std::vector> distros; std::string default_distro; @@ -237,44 +270,25 @@ int get_wsl_information(bool& wsl_available, WSLS& wsls) { return 1; } - wsl_lib = LoadLibrary("wslapi.dll"); - if (!wsl_lib) { - return 1; - } + resource_helper rs; - pWslLaunch = (PWslLaunch) GetProcAddress(wsl_lib, "WslLaunch"); - - if (!pWslLaunch) { - free_resources_and_exit(1); + if (rs.prepare_resources()) { + return 1; } wsl_available = false; + docker_available = false; + docker_compose_available = false; - SECURITY_ATTRIBUTES sa; HANDLE handle; - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - if (!CreatePipe(&out_read, &out_write, &sa, 0)) { - return 1; - } - if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { - return free_resources_and_exit(1); - } - if (!CreatePipe(&in_read, &in_write, &sa, 0)) { - return free_resources_and_exit(1); - } - if (!SetHandleInformation(in_write, HANDLE_FLAG_INHERIT, 0)) { - return free_resources_and_exit(1); - } - for (size_t i = 0; i < distros.size(); ++i) { char wsl_dist_name[256]; char wsl_dist_version[256]; const std::string& distro = distros[i].first; + // skip 'docker-desktop-data' + // Ref: https://stackoverflow.com/a/61431088/4210508 if (distro == "docker-desktop-data"){ continue; } @@ -288,32 +302,32 @@ int get_wsl_information(bool& wsl_available, WSLS& wsls) { wsl.wsl_version = std::to_string(distros[i].second); // lsbrelease - if (!create_wsl_process(distro, command_lsbrelease, &handle)) { + if (!create_wsl_process(rs, distro, command_lsbrelease, &handle)) { continue; } wsl_available = HOST_INFO::parse_linux_os_info( - read_from_pipe(handle), lsbrelease, wsl_dist_name, sizeof(wsl_dist_name), wsl_dist_version, sizeof(wsl_dist_version)); + read_from_pipe(handle, rs.out_read), lsbrelease, wsl_dist_name, sizeof(wsl_dist_name), wsl_dist_version, sizeof(wsl_dist_version)); CloseHandle(handle); if (!wsl_available) { //osrelease const std::string command_osrelease = "cat " + std::string(file_osrelease); - if (!create_wsl_process(distro, command_osrelease, &handle)) { + if (!create_wsl_process(rs, distro, command_osrelease, &handle)) { continue; } wsl_available = HOST_INFO::parse_linux_os_info( - read_from_pipe(handle), osrelease, wsl_dist_name, sizeof(wsl_dist_name), wsl_dist_version, sizeof(wsl_dist_version)); + read_from_pipe(handle, rs.out_read), osrelease, wsl_dist_name, sizeof(wsl_dist_name), wsl_dist_version, sizeof(wsl_dist_version)); CloseHandle(handle); } //redhatrelease if (!wsl_available) { const std::string command_redhatrelease = "cat " + std::string(file_redhatrelease); - if (!create_wsl_process(distro, command_redhatrelease, &handle)) { + if (!create_wsl_process(rs, distro, command_redhatrelease, &handle)) { continue; } wsl_available = HOST_INFO::parse_linux_os_info( - read_from_pipe(handle), redhatrelease, wsl_dist_name, sizeof(wsl_dist_name), wsl_dist_version, sizeof(wsl_dist_version)); + read_from_pipe(handle, rs.out_read), redhatrelease, wsl_dist_name, sizeof(wsl_dist_name), wsl_dist_version, sizeof(wsl_dist_version)); CloseHandle(handle); } @@ -326,16 +340,16 @@ int get_wsl_information(bool& wsl_available, WSLS& wsls) { // sysctl -a const std::string command_sysctl = "sysctl -a"; - if (create_wsl_process(distro, command_sysctl, &handle)) { - parse_sysctl_output(split(read_from_pipe(handle), '\n'), os_name, os_version_extra); + if (create_wsl_process(rs, distro, command_sysctl, &handle)) { + parse_sysctl_output(split(read_from_pipe(handle, rs.out_read), '\n'), os_name, os_version_extra); CloseHandle(handle); } // uname -s if (os_name.empty()) { const std::string command_uname_s = "uname -s"; - if (create_wsl_process(distro, command_uname_s, &handle)) { - os_name = read_from_pipe(handle); + if (create_wsl_process(rs, distro, command_uname_s, &handle)) { + os_name = read_from_pipe(handle, rs.out_read); strip_whitespace(os_name); CloseHandle(handle); } @@ -344,8 +358,8 @@ int get_wsl_information(bool& wsl_available, WSLS& wsls) { // uname -r if (os_version_extra.empty()) { const std::string command_uname_r = "uname -r"; - if (create_wsl_process(distro, command_uname_r ,&handle)) { - os_version_extra = read_from_pipe(handle); + if (create_wsl_process(rs, distro, command_uname_r ,&handle)) { + os_version_extra = read_from_pipe(handle, rs.out_read); strip_whitespace(os_version_extra); CloseHandle(handle); } @@ -363,10 +377,34 @@ int get_wsl_information(bool& wsl_available, WSLS& wsls) { else { wsl.os_version = wsl_dist_version; } + + if (detect_docker) { + if (create_wsl_process(rs, distro, command_get_docker_version, &handle)) { + std::string raw = read_from_pipe(handle, rs.out_read); + std::string version; + wsl.is_docker_available = HOST_INFO::get_docker_version_string(raw, version); + if (wsl.is_docker_available) { + docker_available = true; + wsl.docker_version = version; + } + CloseHandle(handle); + } + if (create_wsl_process(rs, distro, command_get_docker_compose_version, &handle)) { + std::string raw = read_from_pipe(handle, rs.out_read); + std::string version; + wsl.is_docker_compose_available = HOST_INFO::get_docker_compose_version_string(raw, version); + if (wsl.is_docker_compose_available) { + docker_compose_available = true; + wsl.docker_compose_version = version; + } + CloseHandle(handle); + } + } + wsls.wsls.push_back(wsl); } - return free_resources_and_exit(0); + return 0; } #endif // _WIN64 diff --git a/client/log_flags.cpp b/client/log_flags.cpp index 18fd0bcc3bb..634344340f1 100644 --- a/client/log_flags.cpp +++ b/client/log_flags.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2018 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -198,11 +198,8 @@ void CC_CONFIG::show() { if (dont_use_wsl) { msg_printf(NULL, MSG_INFO, "Config: don't use the Windows Subsystem for Linux"); } - if (dont_use_docker){ + if (dont_use_docker) { msg_printf(NULL, MSG_INFO, "Config: don't use the Docker"); - } - if (dont_use_docker_compose){ - msg_printf(NULL, MSG_INFO, "Config: don't use the Docker compose"); } for (i=0; i%d\n" " %d\n" " %d\n" - " %d\n" - " %d\n", + " %d\n", disallow_attach, dont_check_file_sizes, dont_contact_ref_site, @@ -579,8 +576,7 @@ int CC_CONFIG::write(MIOFILE& out, LOG_FLAGS& log_flags) { dont_suspend_nci, dont_use_vbox, dont_use_wsl, - dont_use_docker, - dont_use_docker_compose + dont_use_docker ); for (i=0; i exclude_gpus; std::vector exclusive_apps; std::vector exclusive_gpu_apps; diff --git a/lib/hostinfo.cpp b/lib/hostinfo.cpp index b98cbf809da..a14f5ece62f 100644 --- a/lib/hostinfo.cpp +++ b/lib/hostinfo.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2023 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -71,9 +71,15 @@ void HOST_INFO::clear_host_info() { safe_strcpy(os_name, ""); safe_strcpy(os_version, ""); +#ifdef _WIN64 wsl_available = false; - docker_use =false; +#endif + docker_available = false; + docker_compose_available = false; +#ifndef _WIN64 + safe_strcpy(docker_version, ""); safe_strcpy(docker_compose_version, ""); +#endif #ifdef _WIN64 wsls.clear(); #endif @@ -135,14 +141,17 @@ int HOST_INFO::parse(XML_PARSER& xp, bool static_items_only) { if (xp.parse_str("os_name", os_name, sizeof(os_name))) continue; if (xp.parse_str("os_version", os_version, sizeof(os_version))) continue; #ifdef _WIN64 - if (xp.parse_bool("os_wsl_enabled", wsl_available)) continue; + if (xp.parse_bool("wsl_available", wsl_available)) continue; if (xp.match_tag("wsl")) { this->wsls.parse(xp); continue; } #endif - if (xp.parse_bool("docker_use", docker_use)) continue; + if (xp.parse_bool("docker_available", docker_available)) continue; +#ifndef _WIN64 + if (xp.parse_str("docker_version", docker_version, sizeof(docker_version))) continue; if (xp.parse_str("docker_compose_version", docker_compose_version, sizeof(docker_compose_version))) continue; +#endif if (xp.parse_str("product_name", product_name, sizeof(product_name))) continue; if (xp.parse_str("virtualbox_version", virtualbox_version, sizeof(virtualbox_version))) continue; if (xp.match_tag("coprocs")) { @@ -212,7 +221,11 @@ int HOST_INFO::write( " %s\n" " %d\n" " %d\n" - " %d\n", + " %d\n", +#ifndef _WIN64 + " %s\n", + " %s\n", +#endif host_cpid, p_ncpus, pv, @@ -236,7 +249,11 @@ int HOST_INFO::write( #else 0, #endif - docker_use ? 1 : 0 + docker_available ? 1 : 0 +#ifndef _WIN64 + , docker_version, + docker_compose_version +#endif ); #ifdef _WIN64 if (wsl_available) { @@ -264,14 +281,20 @@ int HOST_INFO::write( buf ); } - if (strlen(docker_compose_version)){ - char buf[256]; - xml_escape(docker_compose_version, buf, sizeof(buf)); +#ifndef _WIN64 + if (docker_available){ + out.printf( + " %s\n", + docker_version + ); + } + if (docker_compose_available){ out.printf( " %s\n", - buf + docker_compose_version ); } +#endif if (include_coprocs) { this->coprocs.write_xml(out, false); } @@ -331,3 +354,27 @@ int HOST_INFO::write_cpu_benchmarks(FILE* out) { ); return 0; } + +bool HOST_INFO::get_docker_version_string(std::string raw, std::string& parsed) { + std::string prefix = "Docker version"; + size_t pos1 = raw.find(prefix); + if (pos1 == std::string::npos) { + return false; + } + size_t pos2 = raw.find(","); + if (pos2 == std::string::npos) { + return false; + } + parsed = raw.substr(pos1 + prefix.size() + 1, pos2 - pos1 - prefix.size() - 1); + return true; +} +bool HOST_INFO::get_docker_compose_version_string(std::string raw, std::string& parsed) { + std::string prefix = "Docker Compose version v"; + size_t pos1 = raw.find(prefix); + if (pos1 == std::string::npos) { + return false; + } + parsed = raw.substr(pos1 + prefix.size(), raw.size() - pos1 - prefix.size()); + return true; +} + diff --git a/lib/hostinfo.h b/lib/hostinfo.h index 6752c00267b..d3c90a8bf70 100644 --- a/lib/hostinfo.h +++ b/lib/hostinfo.h @@ -47,6 +47,8 @@ enum LINUX_OS_INFO_PARSER { const char command_lsbrelease[] = "/usr/bin/lsb_release -a 2>&1"; const char file_osrelease[] = "/etc/os-release"; const char file_redhatrelease[] = "/etc/redhat-release"; +const char command_get_docker_version[] = "docker --version"; +const char command_get_docker_compose_version[] = "docker compose version"; // if you add fields, update clear_host_info() @@ -80,11 +82,19 @@ class HOST_INFO { char os_name[256]; char os_version[256]; - // WSL information for Win10 only - bool wsl_available; - bool docker_use; + bool docker_available; + bool docker_compose_available; +#ifndef _WIN64 + // on Windows we can have several docker installation within WSL + // that is why it makes no sense to have this information put here + // instead the information about the available 'docker' and 'docker compose' + // installations should be taken from every particular WSL distro + char docker_version[256]; char docker_compose_version[256]; +#endif #ifdef _WIN64 + // WSL information for Win10 only + bool wsl_available; WSLS wsls; #endif @@ -126,8 +136,14 @@ class HOST_INFO { int get_host_battery_state(); int get_local_network_info(); int get_virtualbox_version(); - int get_docker_info(bool& docker_use); +#ifndef _WIN64 + // on Windows we can have several docker installation within WSL + // that is why it makes no sense to have this information put here + // instead the information about the available 'docker' and 'docker compose' + // installations should be taken from every particular WSL distro + int get_docker_info(); int get_docker_compose_info(); +#endif void make_random_string(const char* salt, char* out); void generate_host_cpid(); static bool parse_linux_os_info( @@ -146,6 +162,8 @@ class HOST_INFO { char* os_name, const int os_name_size, char* os_version, const int os_version_size ); + static bool get_docker_version_string(std::string raw, std::string& parsed); + static bool get_docker_compose_version_string(std::string raw, std::string& parsed); #ifdef _WIN32 void win_get_processor_info(); #endif @@ -154,7 +172,7 @@ class HOST_INFO { extern void make_secure_random_string(char*); #ifdef _WIN64 -extern int get_wsl_information(bool& wsl_available, WSLS& wsls); +extern int get_wsl_information(bool& wsl_available, WSLS& wsls, bool detect_docker, bool& docker_available, bool& docker_compose_available); extern int get_processor_group(HANDLE); #endif diff --git a/lib/wslinfo.cpp b/lib/wslinfo.cpp index f39091e6191..5f4355884b6 100644 --- a/lib/wslinfo.cpp +++ b/lib/wslinfo.cpp @@ -27,6 +27,10 @@ void WSL::clear() { os_version = ""; is_default = false; wsl_version = "1"; + is_docker_available = false; + is_docker_compose_available = false; + docker_version = ""; + docker_compose_version = ""; } void WSL::write_xml(MIOFILE& f) { @@ -41,12 +45,20 @@ void WSL::write_xml(MIOFILE& f) { " %s\n" " %d\n" " %s\n" + " %d\n" + " %d\n" + " %s\n" + " %s\n" " \n", dn, n, v, is_default ? 1 : 0, - wsl_version.c_str() + wsl_version.c_str(), + is_docker_available ? 1 : 0, + is_docker_compose_available ? 1 : 0, + docker_version.c_str(), + docker_compose_version.c_str() ); } @@ -61,6 +73,10 @@ int WSL::parse(XML_PARSER& xp) { if (xp.parse_string("os_version", os_version)) continue; if (xp.parse_bool("is_default", is_default)) continue; if (xp.parse_string("wsl_version", wsl_version)) continue; + if (xp.parse_bool("is_docker_available", is_docker_available)) continue; + if (xp.parse_bool("is_docker_compose_available", is_docker_compose_available)) continue; + if (xp.parse_string("docker_version", docker_version)) continue; + if (xp.parse_string("docker_compose_version", docker_compose_version)) continue; } return ERR_XML_PARSE; } diff --git a/lib/wslinfo.h b/lib/wslinfo.h index 734070c882f..15680102b54 100644 --- a/lib/wslinfo.h +++ b/lib/wslinfo.h @@ -35,6 +35,14 @@ struct WSL { std::string wsl_version; // flag indicating whether this is the default WSL distribution bool is_default; + // flag indicating whether Docker is available in this WSL distribution + bool is_docker_available; + // flag indicating whether Docker Compose is available in this WSL distribution + bool is_docker_compose_available; + // version of Docker installed in this WSL distribution + std::string docker_version; + // version of Docker Compose installed in this WSL distribution + std::string docker_compose_version; WSL(); diff --git a/sched/plan_class_spec.cpp b/sched/plan_class_spec.cpp index 0b8a48f606c..9c1e49a7642 100644 --- a/sched/plan_class_spec.cpp +++ b/sched/plan_class_spec.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2023 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -456,23 +456,120 @@ bool PLAN_CLASS_SPEC::check( } if (docker){ - if (sreq.core_client_major_version < 8) { - add_no_work_message("BOINC client 8.0+ required for Docker jobs"); - return false; - } - if (!(sreq.host.docker_use)) { + if (!(sreq.host.docker_available)) { add_no_work_message("Docker is not installed or is not available"); return false; } - if ((strstr(docker_compose_version, "v1")) && (!(strstr(sreq.host.docker_compose_version, "v1")))){ - add_no_work_message("Docker compose (older version: docker-compose) is required, but is not installed or is not available"); + if (docker_compose && !(sreq.host.docker_compose_available)) { + add_no_work_message("Docker compose is not installed or is not available"); return false; } + if (sreq.host.wsl_available) { + bool docker_found = false; + bool docker_compose_found = false; + for (int i = 0; i < sreq.host.wsls.wsls.size(); i++) { + if (sreq.host.wsls.wsls[i].docker_available) { + if (docker_compose && sreq.host.wsls.wsls[i].docker_compose_available) { + int maj, min, rel; + int n = sscanf(sreq.host.wsls.wsls[i].docker_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker version\n" + ); + continue; + } + } + int v = maj*10000 + min*100 + rel; + if (min_docker_version && v < min_docker_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker version too low: %d < %d\n", + v, min_docker_version + ); + continue; + } + } + docker_found = true; + } + if (!docker_compose && docker_found) { + break; + } + } + if (docker_compose && sreq.host.wsls.wsls[i].docker_compose_available) { + int maj, min, rel; + int n = sscanf(sreq.host.wsls.wsls[i].docker_compose_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker compose version\n" + ); + continue; + } + } + int v = maj*10000 + min*100 + rel; + if (min_docker_compose_version && v < min_docker_compose_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker compose version too low: %d < %d\n", + v, min_docker_compose_version + ); + continue; + } + } + docker_compose_found = true; + } + } + if (!docker_found) { + add_no_work_message("Suitable Docker is not installed or is not available"); + return false; + } + if (docker_compose && !docker_compose_found) { + add_no_work_message("Suitable Docker compose is not installed or is not available"); + return false; + } + } else { + int maj, min, rel; + int n = sscanf(sreq.host.docker_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker version\n" + ); + } + return false; + } + int v = maj*10000 + min*100 + rel; + if (min_docker_version && v < min_docker_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker version too low: %d < %d\n", + v, min_docker_version + ); + } + return false; + } - if ((strstr(docker_compose_version, "v2")) && (!(strstr(sreq.host.docker_compose_version, "v2")))){ - add_no_work_message("Docker compose (newer version: docker compose) is required, but is not installed or is not available"); - return false; + n = sscanf(sreq.host.docker_compose_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker compose version\n" + ); + } + return false; + } + v = maj*10000 + min*100 + rel; + if (min_docker_compose_version && v < min_docker_compose_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker compose version too low: %d < %d\n", + v, min_docker_compose_version + ); + } + return false; + } } } @@ -1121,7 +1218,9 @@ int PLAN_CLASS_SPEC::parse(XML_PARSER& xp) { if (xp.parse_bool("opencl", opencl)) continue; if (xp.parse_bool("virtualbox", virtualbox)) continue; if (xp.parse_bool("docker", docker)) continue; - if (xp.parse_str("docker_compose_version", docker_compose_version, sizeof(docker_compose_version))) continue; + if (xp.parse_bool("docker_compose", docker_compose)) continue; + if (xp.parse_int("min_docker_version", min_docker_version)) continue; + if (xp.parse_int("min_docker_compose_version", min_docker_compose_version)) continue; if (xp.parse_bool("is64bit", is64bit)) continue; if (xp.parse_str("cpu_feature", buf, sizeof(buf))) { cpu_features.push_back(" " + (string)buf + " "); @@ -1269,7 +1368,9 @@ PLAN_CLASS_SPEC::PLAN_CLASS_SPEC() { opencl = false; virtualbox = false; docker = false; - strcpy(docker_compose_version, ""); + docker_compose = false; + min_docker_version = 0; + min_docker_compose_version = 0; is64bit = false; min_ncpus = 0; max_threads = 1; diff --git a/sched/plan_class_spec.h b/sched/plan_class_spec.h index db3be1c939b..758a8017bbf 100644 --- a/sched/plan_class_spec.h +++ b/sched/plan_class_spec.h @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2023 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -33,7 +33,9 @@ struct PLAN_CLASS_SPEC { bool opencl; bool virtualbox; bool docker; - char docker_compose_version[256]; + bool docker_compose; + int min_docker_version; + int min_docker_compose_version; bool is64bit; std::vector cpu_features; double min_ncpus; diff --git a/sched/plan_class_spec.xml.sample b/sched/plan_class_spec.xml.sample index bfc1ecb031c..45988c648ee 100644 --- a/sched/plan_class_spec.xml.sample +++ b/sched/plan_class_spec.xml.sample @@ -120,4 +120,11 @@ 2 2 + + docker + + + 270102 + 32901 + diff --git a/sched/sched_customize.cpp b/sched/sched_customize.cpp index 62de1585f3c..f647ca94a84 100644 --- a/sched/sched_customize.cpp +++ b/sched/sched_customize.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2008 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -869,40 +869,135 @@ static inline bool app_plan_opencl( } } -// handles vbox[32|64][_[mt]|[hwaccel]] -// "mt" is tailored to the needs of CERN: -// use 1 or 2 CPUs - - //plan class for Docker jobs // static inline bool app_plan_docker( SCHEDULER_REQUEST& sreq, char* plan_class ){ - if (sreq.core_client_major_version < 8) { - add_no_work_message("BOINC client 8.0+ required for Docker jobs"); - return false; - } - - if (!(sreq.host.docker_use)) { - add_no_work_message("Docker is not installed or is not available"); - return false; - } + if (docker){ + if (!(sreq.host.docker_available)) { + add_no_work_message("Docker is not installed or is not available"); + return false; + } - if ((strstr(plan_class, "v1")) && (!(strstr(sreq.host.docker_compose_version, "v1")))){ - add_no_work_message("Docker compose (older version: docker-compose) is required, but is not installed or is not available"); - return false; - } + if (docker_compose && !(sreq.host.docker_compose_available)) { + add_no_work_message("Docker compose is not installed or is not available"); + return false; + } + if (sreq.host.wsl_available) { + bool docker_found = false; + bool docker_compose_found = false; + for (int i = 0; i < sreq.host.wsls.wsls.size(); i++) { + if (sreq.host.wsls.wsls[i].docker_available) { + if (docker_compose && sreq.host.wsls.wsls[i].docker_compose_available) { + int maj, min, rel; + int n = sscanf(sreq.host.wsls.wsls[i].docker_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker version\n" + ); + continue; + } + } + int v = maj*10000 + min*100 + rel; + if (min_docker_version && v < min_docker_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker version too low: %d < %d\n", + v, min_docker_version + ); + continue; + } + } + docker_found = true; + } + if (!docker_compose && docker_found) { + break; + } + } + if (docker_compose && sreq.host.wsls.wsls[i].docker_compose_available) { + int maj, min, rel; + int n = sscanf(sreq.host.wsls.wsls[i].docker_compose_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker compose version\n" + ); + continue; + } + } + int v = maj*10000 + min*100 + rel; + if (min_docker_compose_version && v < min_docker_compose_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker compose version too low: %d < %d\n", + v, min_docker_compose_version + ); + continue; + } + } + docker_compose_found = true; + } + } + if (!docker_found) { + add_no_work_message("Suitable Docker is not installed or is not available"); + return false; + } + if (docker_compose && !docker_compose_found) { + add_no_work_message("Suitable Docker compose is not installed or is not available"); + return false; + } + } else { + int maj, min, rel; + int n = sscanf(sreq.host.docker_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker version\n" + ); + } + return false; + } + int v = maj*10000 + min*100 + rel; + if (min_docker_version && v < min_docker_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker version too low: %d < %d\n", + v, min_docker_version + ); + } + return false; + } - if ((strstr(plan_class, "v2")) && (!(strstr(sreq.host.docker_compose_version, "v2")))){ - add_no_work_message("Docker compose (newer version: docker compose) is required, but is not installed or is not available"); - return false; + n = sscanf(sreq.host.docker_compose_version, "%d.%d.%d", &maj, &min, &rel); + if (n != 3) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: can't parse docker compose version\n" + ); + } + return false; + } + v = maj*10000 + min*100 + rel; + if (min_docker_compose_version && v < min_docker_compose_version) { + if (config.debug_version_select) { + log_messages.printf(MSG_NORMAL, + "[version] plan_class_spec: docker compose version too low: %d < %d\n", + v, min_docker_compose_version + ); + } + return false; + } + } } return true; - } +// handles vbox[32|64][_[mt]|[hwaccel]] +// "mt" is tailored to the needs of CERN: +// use 1 or 2 CPUs static inline bool app_plan_vbox( SCHEDULER_REQUEST& sreq, char* plan_class, HOST_USAGE& hu diff --git a/sched/sched_types.cpp b/sched/sched_types.cpp index a50be57bc4a..71a2cbb50ce 100644 --- a/sched/sched_types.cpp +++ b/sched/sched_types.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. -// http://boinc.berkeley.edu -// Copyright (C) 2023 University of California +// https://boinc.berkeley.edu +// Copyright (C) 2024 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -1401,7 +1401,9 @@ int HOST::parse(XML_PARSER& xp) { if (xp.parse_double("n_bwup", n_bwup)) continue; if (xp.parse_double("n_bwdown", n_bwdown)) continue; if (xp.parse_str("p_features", p_features, sizeof(p_features))) continue; - if (xp.parse_bool("docker_use", docker_use)) continue; + if (xp.parse_bool("docker_available", docker_available)) continue; + if (xp.parse_bool("docker_compose_available", docker_compose_available)) continue; + if (xp.parse_str("docker_version", docker_version, sizeof(docker_version))) continue; if (xp.parse_str("docker_compose_version", docker_compose_version, sizeof(docker_compose_version))) continue; if (xp.parse_str("virtualbox_version", virtualbox_version, sizeof(virtualbox_version))) continue; if (xp.parse_bool("p_vm_extensions_disabled", p_vm_extensions_disabled)) continue;