diff --git a/e2etests/orchestration/common/common.go b/e2etests/orchestration/common/common.go index da5163bb8a8..2d447255e0a 100644 --- a/e2etests/orchestration/common/common.go +++ b/e2etests/orchestration/common/common.go @@ -83,7 +83,6 @@ func verifyZipFile(filename string) error { func VerifyLogsEndpoint(srvURL, group, name string) error { base := fmt.Sprintf("%s/cvds/%s/%s/logs/", srvURL, group, name) urls := []string{ - base, base + "launcher.log", base + "kernel.log", } diff --git a/e2etests/orchestration/instance_logs_test/BUILD.bazel b/e2etests/orchestration/instance_logs_test/BUILD.bazel new file mode 100644 index 00000000000..99f3eb10a55 --- /dev/null +++ b/e2etests/orchestration/instance_logs_test/BUILD.bazel @@ -0,0 +1,26 @@ +# Copyright (C) 2026 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_go//go:def.bzl", "go_test") + +go_test( + name = "instance_logs_test_test", + srcs = ["main_test.go"], + size = "medium", + deps = [ + "//orchestration/common", + "@com_github_google_android_cuttlefish_frontend//src/host_orchestrator/api/v1:api", + "@com_github_google_android_cuttlefish_frontend//src/libhoclient:libhoclient", + ], +) diff --git a/e2etests/orchestration/instance_logs_test/main_test.go b/e2etests/orchestration/instance_logs_test/main_test.go new file mode 100644 index 00000000000..3213e7d2b9d --- /dev/null +++ b/e2etests/orchestration/instance_logs_test/main_test.go @@ -0,0 +1,146 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "encoding/json" + "errors" + "io" + "log" + "net/http" + "testing" + "time" + + "github.com/google/android-cuttlefish/e2etests/orchestration/common" + hoapi "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/api/v1" + hoclient "github.com/google/android-cuttlefish/frontend/src/libhoclient" +) + +const baseURL = "http://0.0.0.0:2080" + +func TestInstanceLogs(t *testing.T) { + srv := hoclient.NewHostOrchestratorClient(baseURL) + t.Cleanup(func() { + if err := common.CollectHOLogs(baseURL); err != nil { + log.Printf("failed to collect HO logs: %s", err) + } + }) + config := ` + { + "instances": [ + { + "vm": { + "crosvm": { + "enable_sandbox": false + }, + "memory_mb": 8192, + "setupwizard_mode": "OPTIONAL", + "cpus": 8 + }, + "disk": { + "default_build": "@ab/aosp-android-latest-release/aosp_cf_x86_64_only_phone-userdebug", + "download_img_zip": true + } + } + ] + } + ` + envConfig := make(map[string]interface{}) + if err := json.Unmarshal([]byte(config), &envConfig); err != nil { + t.Fatal(err) + } + createReq := &hoapi.CreateCVDRequest{ + EnvConfig: envConfig, + } + if _, err := srv.CreateCVD(createReq, &hoclient.AccessTokenBuildAPICreds{}); err != nil { + t.Fatal(err) + } + oneSecStreamChan := make(chan result) + twoSecsStreamChan := make(chan result) + go stream(1*time.Second, oneSecStreamChan) + go stream(30*time.Second, twoSecsStreamChan) + oneSecRes := <-oneSecStreamChan + twoSecsRes := <-twoSecsStreamChan + + if oneSecRes.err != nil { + t.Fatal(oneSecRes.err) + } + if twoSecsRes.err != nil { + t.Fatal(twoSecsRes.err) + } + if oneSecRes.read == 0 { + t.Fatal("0 bytes read") + } + diff := twoSecsRes.read - oneSecRes.read + delta := 50 + if diff <= delta { + t.Fatalf("expected stream delta bigger than: %d, got: %d", delta, diff) + } + + // Verifies stream stops when device is deleted + stopStreamChan := make(chan result) + start := time.Now() + go stream(1*time.Minute, stopStreamChan) + go func() { + if err := srv.Reset(); err != nil { + log.Printf("reset failed %v", err) + } + }() + res := <-stopStreamChan + if res.err != nil { + t.Fatal(res.err) + } + duration := time.Since(start) + if duration >= 1*time.Minute { + t.Fatal("stream not stopped in less than 1 minute after `cvd reset`") + } +} + +type result struct { + read int + err error +} + +func stream(duration time.Duration, ch chan result) { + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + url := "http://0.0.0.0:2080/cvds/cvd_1/1/logs/logcat/:stream" + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + ch <- result{err: err} + return + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + ch <- result{err: err} + return + } + defer resp.Body.Close() + totalRead := 0 + buffer := make([]byte, 1024) + for { + n, err := resp.Body.Read(buffer) + if err != nil { + if err == io.EOF || errors.Is(err, context.DeadlineExceeded) { + ch <- result{read: totalRead} + } else { + ch <- result{err: err} + } + return + } + totalRead += n + } +} diff --git a/frontend/go.work b/frontend/go.work index 4bb7b44bb4d..faafd17f83d 100644 --- a/frontend/go.work +++ b/frontend/go.work @@ -1,4 +1,4 @@ -go 1.18 +go 1.23 use ( ./src/host_orchestrator diff --git a/frontend/go.work.sum b/frontend/go.work.sum new file mode 100644 index 00000000000..d8a28e29f0f --- /dev/null +++ b/frontend/go.work.sum @@ -0,0 +1,3 @@ +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= diff --git a/frontend/src/host_orchestrator/go.mod b/frontend/src/host_orchestrator/go.mod index a51932e2287..4b5f3a8e450 100644 --- a/frontend/src/host_orchestrator/go.mod +++ b/frontend/src/host_orchestrator/go.mod @@ -1,6 +1,6 @@ module github.com/google/android-cuttlefish/frontend/src/host_orchestrator -go 1.17 +go 1.23 require ( github.com/google/android-cuttlefish/frontend/src/liboperator v0.0.0-20240822182916-7bea0dafdbde @@ -12,10 +12,11 @@ require ( ) require ( + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect - golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.3.0 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect google.golang.org/grpc v1.40.0 // indirect diff --git a/frontend/src/host_orchestrator/go.sum b/frontend/src/host_orchestrator/go.sum index 1e84a2343dc..210b0b0be64 100644 --- a/frontend/src/host_orchestrator/go.sum +++ b/frontend/src/host_orchestrator/go.sum @@ -16,6 +16,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -51,6 +53,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -83,6 +87,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/frontend/src/host_orchestrator/orchestrator/controller.go b/frontend/src/host_orchestrator/orchestrator/controller.go index e4d227bc561..332a75a9df0 100644 --- a/frontend/src/host_orchestrator/orchestrator/controller.go +++ b/frontend/src/host_orchestrator/orchestrator/controller.go @@ -17,6 +17,7 @@ package orchestrator import ( "encoding/json" "fmt" + "io" "log" "mime/multipart" "net/http" @@ -26,6 +27,7 @@ import ( "strconv" "time" + "github.com/fsnotify/fsnotify" apiv1 "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/api/v1" "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/cvd" "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/debug" @@ -67,7 +69,8 @@ func (c *Controller) AddRoutes(router *mux.Router) { router.Handle("/cvds", httpHandler(&listCVDsHandlerAll{Config: c.Config})).Methods("GET") router.Handle("/cvds/{group}", httpHandler(&listCVDsHandler{Config: c.Config})).Methods("GET") router.Handle("/cvds/{group}/{name}", httpHandler(&listCVDsHandler{Config: c.Config})).Methods("GET") - router.PathPrefix("/cvds/{group}/{name}/logs").Handler(&getCVDLogsHandler{Config: c.Config}).Methods("GET") + router.Handle("/cvds/{group}/{name}/logs/{logname}", &getCVDLogsHandler{Config: c.Config}).Methods("GET") + router.Handle("/cvds/{group}/{name}/logs/{logname}/:stream", &streamInstanceLogFileHandler{}).Methods("GET") router.Handle("/cvds/{group}/:start", httpHandler(newExecCVDGroupCommandHandler(c.Config, c.OperationManager, &startCvdCommand{}))).Methods("POST") router.Handle("/cvds/{group}/:stop", @@ -536,20 +539,44 @@ func (h *getCVDLogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) group := vars["group"] name := vars["name"] + logname := vars["logname"] logsDir, err := CVDLogsDir(exec.CommandContext, group, name) if err != nil { log.Printf("request %q failed with error: %v", r.Method+" "+r.URL.Path, err) - appErr, ok := err.(*operator.AppError) - if ok { - w.WriteHeader(appErr.StatusCode) - } else { - w.WriteHeader(http.StatusInternalServerError) - } + w.WriteHeader(http.StatusInternalServerError) return } - sp := fmt.Sprintf("/cvds/%s/%s/logs", group, name) - handler := http.StripPrefix(sp, http.FileServer(http.Dir(logsDir))) - handler.ServeHTTP(w, r) + filePath := filepath.Join(logsDir, logname) + file, err := os.Open(filePath) + if err != nil { + log.Printf("request %q failed with error: %v", r.Method+" "+r.URL.Path, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer file.Close() + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Transfer-Encoding", "chunked") + flusher, ok := w.(http.Flusher) + if !ok { + log.Println("ResponseWriter does not support Flusher") + } + buffer := make([]byte, 4096) + for { + n, err := file.Read(buffer) + if n > 0 { + w.Write(buffer[:n]) + if flusher != nil { + flusher.Flush() + } + } + if err != nil { + if err == io.EOF { + break + } + log.Printf("Error reading file: %v", err) + return + } + } } type listOperationsHandler struct { @@ -923,3 +950,111 @@ func getFetchCredentials(config BuildAPICredentialsConfig, r *http.Request) cvd. log.Println("fetch credentials: using no credentials") return cvd.FetchCredentials{} } + +type streamInstanceLogFileHandler struct{} + +func (h *streamInstanceLogFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Println("request:", r.Method, r.URL.Path) + vars := mux.Vars(r) + group := vars["group"] + name := vars["name"] + logname := vars["logname"] + logsDir, err := CVDLogsDir(exec.CommandContext, group, name) + if err != nil { + log.Printf("request %q failed with error: %v", r.Method+" "+r.URL.Path, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + filePath := filepath.Join(logsDir, logname) + file, err := os.Open(filePath) + if err != nil { + log.Printf("request %q failed with error: %v", r.Method+" "+r.URL.Path, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer file.Close() + + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Transfer-Encoding", "chunked") + flusher, ok := w.(http.Flusher) + if !ok { + log.Println("ResponseWriter does not support Flusher") + } + buffer := make([]byte, 4096) + + // Read existing content first + for { + n, err := file.Read(buffer) + if n > 0 { + w.Write(buffer[:n]) + if flusher != nil { + flusher.Flush() + } + } + if err != nil { + if err == io.EOF { + break + } + log.Printf("Error reading file: %v", err) + return + } + } + // Set up fsnotify watcher + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Printf("Failed to create watcher: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer watcher.Close() + + // Watch the directory to reliably detect file deletion + err = watcher.Add(logsDir) + if err != nil { + log.Printf("Failed to add directory to watcher: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + for { + select { + case <-r.Context().Done(): + log.Println("Client disconnected") + return + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("Watcher error: %v", err) + return + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Name == filePath { + if event.Has(fsnotify.Write) { + for { + n, err := file.Read(buffer) + if n > 0 { + w.Write(buffer[:n]) + if flusher != nil { + flusher.Flush() + } + } + if err != nil { + if err == io.EOF { + break + } + log.Printf("Error reading file: %v", err) + return + } + } + } + if event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) { + log.Printf("File was deleted or renamed: %v", event) + return // Stop streaming + } + } + } + } +} diff --git a/frontend/src/host_orchestrator/orchestrator/createcvdaction.go b/frontend/src/host_orchestrator/orchestrator/createcvdaction.go index f40b7c3d17b..d60406ab331 100644 --- a/frontend/src/host_orchestrator/orchestrator/createcvdaction.go +++ b/frontend/src/host_orchestrator/orchestrator/createcvdaction.go @@ -16,7 +16,6 @@ package orchestrator import ( "encoding/json" - "io/ioutil" "log" "os" "strings" @@ -117,9 +116,9 @@ func validateRequest(r *apiv1.CreateCVDRequest) error { return nil } -// See https://pkg.go.dev/io/ioutil@go1.13.15#TempFile +// See https://pkg.go.dev/os#CreateTemp func createTempFile(pattern string, data string, mode os.FileMode) (*os.File, error) { - file, err := ioutil.TempFile("", pattern) + file, err := os.CreateTemp("", pattern) if err != nil { return nil, err } diff --git a/frontend/src/host_orchestrator/orchestrator/displayscreenshotaction.go b/frontend/src/host_orchestrator/orchestrator/displayscreenshotaction.go index 5901d358601..aeada111ef4 100644 --- a/frontend/src/host_orchestrator/orchestrator/displayscreenshotaction.go +++ b/frontend/src/host_orchestrator/orchestrator/displayscreenshotaction.go @@ -16,7 +16,6 @@ package orchestrator import ( "encoding/base64" - "io/ioutil" "log" "os" "path/filepath" @@ -64,7 +63,7 @@ func (a *DisplayScreenshotAction) Run() (apiv1.Operation, error) { } func (a *DisplayScreenshotAction) createScreenshot(op apiv1.Operation) (*apiv1.DisplayScreenshotResponse, error) { - tempdir, err := ioutil.TempDir("", "screenshot") + tempdir, err := os.MkdirTemp("", "screenshot") if err != nil { return nil, operator.NewInternalError("failed to create temp directory", err) } @@ -77,7 +76,7 @@ func (a *DisplayScreenshotAction) createScreenshot(op apiv1.Operation) (*apiv1.D return nil, operator.NewInternalError("cvd display screenshot failed", err) } - screenshotBytes, err := ioutil.ReadFile(screenshotPath) + screenshotBytes, err := os.ReadFile(screenshotPath) if err != nil { return nil, operator.NewInternalError("failed to read screenshot file", err) } diff --git a/frontend/src/host_orchestrator/orchestrator/imagedirectories.go b/frontend/src/host_orchestrator/orchestrator/imagedirectories.go index f59434f249d..28156702ba7 100644 --- a/frontend/src/host_orchestrator/orchestrator/imagedirectories.go +++ b/frontend/src/host_orchestrator/orchestrator/imagedirectories.go @@ -16,7 +16,6 @@ package orchestrator import ( "fmt" - "io/ioutil" "os" "path/filepath" @@ -69,7 +68,7 @@ func (m *ImageDirectoriesManagerImpl) ListImageDirectories() ([]string, error) { } else if !exists { return imageDirs, nil } - entries, err := ioutil.ReadDir(m.RootDir) + entries, err := os.ReadDir(m.RootDir) if err != nil { return nil, fmt.Errorf("failed to read directory: %w", err) } @@ -94,7 +93,7 @@ func (m *ImageDirectoriesManagerImpl) UpdateImageDirectory(imageDirName, dir str } else if !exists { return operator.NewNotFoundError(fmt.Sprintf("image directory(dir:%q) not found", imageDirName), nil) } - entries, err := ioutil.ReadDir(dir) + entries, err := os.ReadDir(dir) if err != nil { return fmt.Errorf("failed to read directory: %w", err) } diff --git a/frontend/src/host_orchestrator/orchestrator/imagedirectories_test.go b/frontend/src/host_orchestrator/orchestrator/imagedirectories_test.go index 30c3b2662e1..4d64846121c 100644 --- a/frontend/src/host_orchestrator/orchestrator/imagedirectories_test.go +++ b/frontend/src/host_orchestrator/orchestrator/imagedirectories_test.go @@ -15,7 +15,6 @@ package orchestrator import ( - "io/ioutil" "os" "path/filepath" "testing" @@ -86,14 +85,14 @@ func TestUpdateImageDirectorySucceeds(t *testing.T) { t.Fatal(err) } defer f.Close() - if err := ioutil.WriteFile(src, []byte("hello_world"), 0600); err != nil { + if err := os.WriteFile(src, []byte("hello_world"), 0600); err != nil { t.Fatal(err) } if err := idm.UpdateImageDirectory(imageDir, srcDir); err != nil { t.Fatal(err) } - if output, err := ioutil.ReadFile(filepath.Join(rootDir, imageDir, "foo.txt")); err != nil { + if output, err := os.ReadFile(filepath.Join(rootDir, imageDir, "foo.txt")); err != nil { t.Fatal(err) } else if diff := cmp.Diff("hello_world", string(output)); diff != "" { t.Errorf("response mismatch (-want +got):\n%s", diff) @@ -117,20 +116,20 @@ func TestUpdateImageDirectorySucceedsWithModification(t *testing.T) { t.Fatal(err) } defer f.Close() - if err := ioutil.WriteFile(src, []byte("hello_world"), 0600); err != nil { + if err := os.WriteFile(src, []byte("hello_world"), 0600); err != nil { t.Fatal(err) } if err := idm.UpdateImageDirectory(imageDir, srcDir); err != nil { t.Fatal(err) } - if err := ioutil.WriteFile(src, []byte("hello_world_again"), 0600); err != nil { + if err := os.WriteFile(src, []byte("hello_world_again"), 0600); err != nil { t.Fatal(err) } if err := idm.UpdateImageDirectory(imageDir, srcDir); err != nil { t.Fatal(err) } - if output, err := ioutil.ReadFile(filepath.Join(rootDir, imageDir, "foo.txt")); err != nil { + if output, err := os.ReadFile(filepath.Join(rootDir, imageDir, "foo.txt")); err != nil { t.Fatal(err) } else if diff := cmp.Diff("hello_world_again", string(output)); diff != "" { t.Errorf("response mismatch (-want +got):\n%s", diff) @@ -182,7 +181,7 @@ func TestDeleteImageDirectorySucceeds(t *testing.T) { t.Fatal(err) } defer f.Close() - if err := ioutil.WriteFile(src, []byte("hello_world"), 0600); err != nil { + if err := os.WriteFile(src, []byte("hello_world"), 0600); err != nil { t.Fatal(err) } if err := idm.UpdateImageDirectory(imageDir, srcDir); err != nil { diff --git a/frontend/src/host_orchestrator/orchestrator/testing/testing.go b/frontend/src/host_orchestrator/orchestrator/testing/testing.go index dbc3d5ce107..04882b4c545 100644 --- a/frontend/src/host_orchestrator/orchestrator/testing/testing.go +++ b/frontend/src/host_orchestrator/orchestrator/testing/testing.go @@ -1,7 +1,6 @@ package testing import ( - "io/ioutil" "os" "testing" ) @@ -13,7 +12,7 @@ import ( // Similar to https://pkg.go.dev/testing#T.TempDir without the cleanup part. testing#T.TempDir cannot be used // as it was introduced in go 1.15. func TempDir(t *testing.T) string { - name, err := ioutil.TempDir("", "cuttlefishTestDir") + name, err := os.MkdirTemp("", "cuttlefishTestDir") if err != nil { t.Fatal(err) } diff --git a/frontend/src/host_orchestrator/orchestrator/userartifacts.go b/frontend/src/host_orchestrator/orchestrator/userartifacts.go index 20611b87d90..ea0315c4af7 100644 --- a/frontend/src/host_orchestrator/orchestrator/userartifacts.go +++ b/frontend/src/host_orchestrator/orchestrator/userartifacts.go @@ -21,7 +21,6 @@ import ( "crypto/sha256" "fmt" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -156,7 +155,7 @@ func (m *UserArtifactsManagerImpl) ExtractArtifact(checksum string) error { if err != nil { return err } - workDir, err := ioutil.TempDir(m.WorkDir, "") + workDir, err := os.MkdirTemp(m.WorkDir, "") if err != nil { return err } @@ -288,7 +287,7 @@ func (m *UserArtifactsManagerImpl) getFilePath(checksum string) (string, error) } else if !exists { return "", operator.NewNotFoundError(fmt.Sprintf("user artifact(checksum:%q) not found", checksum), nil) } - if entries, err := ioutil.ReadDir(dir); err != nil { + if entries, err := os.ReadDir(dir); err != nil { return "", fmt.Errorf("failed to read directory where user artifact located: %w", err) } else if len(entries) != 1 || entries[0].IsDir() { return "", fmt.Errorf("directory where user artifact located should contain a single file only") diff --git a/frontend/src/host_orchestrator/orchestrator/userartifacts_test.go b/frontend/src/host_orchestrator/orchestrator/userartifacts_test.go index e9e82bf8896..2da2ec288df 100644 --- a/frontend/src/host_orchestrator/orchestrator/userartifacts_test.go +++ b/frontend/src/host_orchestrator/orchestrator/userartifacts_test.go @@ -21,7 +21,6 @@ import ( "compress/gzip" "crypto/sha256" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -59,7 +58,7 @@ func TestUpdateArtifactWithSingleChunkSucceeds(t *testing.T) { if err := uam.UpdateArtifact(checksum, chunk); err != nil { t.Fatal(err) } - b, err := ioutil.ReadFile(filepath.Join(rootDir, checksum, testFileName)) + b, err := os.ReadFile(filepath.Join(rootDir, checksum, testFileName)) if err != nil { t.Fatal(err) } @@ -158,7 +157,7 @@ func TestUpdateArtifactWithMultipleSerialChunkSucceeds(t *testing.T) { t.Fatal(err) } } - b, err := ioutil.ReadFile(filepath.Join(rootDir, checksum, testFileName)) + b, err := os.ReadFile(filepath.Join(rootDir, checksum, testFileName)) if err != nil { t.Fatal(err) } @@ -189,7 +188,7 @@ func TestUpdateArtifactWithMultipleParallelChunkSucceeds(t *testing.T) { }(chunk) } wg.Wait() - b, err := ioutil.ReadFile(filepath.Join(rootDir, checksum, testFileName)) + b, err := os.ReadFile(filepath.Join(rootDir, checksum, testFileName)) if err != nil { t.Fatal(err) } @@ -257,7 +256,7 @@ func TestExtractArtifactSucceedsWithZipFormat(t *testing.T) { if err != nil { t.Fatal(err) } - data, err := ioutil.ReadFile(zipFile) + data, err := os.ReadFile(zipFile) if err != nil { t.Fatal(err) } @@ -303,7 +302,7 @@ func TestExtractArtifactSucceedsWithTarGzFormat(t *testing.T) { if err != nil { t.Fatal(err) } - data, err := ioutil.ReadFile(tarFile) + data, err := os.ReadFile(tarFile) if err != nil { t.Fatal(err) } @@ -368,7 +367,7 @@ func TestExtractArtifactAfterArtifactIsFullyExtractedFails(t *testing.T) { if err != nil { t.Fatal(err) } - data, err := ioutil.ReadFile(archive) + data, err := os.ReadFile(archive) if err != nil { t.Fatal(err) } @@ -594,7 +593,7 @@ func getContents(dir string) (map[string]string, error) { } if relPath, err := filepath.Rel(dir, path); err != nil { return err - } else if content, err := ioutil.ReadFile(path); err != nil { + } else if content, err := os.ReadFile(path); err != nil { return err } else { contents[relPath] = string(content) @@ -608,7 +607,7 @@ func getContents(dir string) (map[string]string, error) { } func createZip(dir string, contents map[string]string) (string, error) { - zipFile, err := ioutil.TempFile(dir, "*.zip") + zipFile, err := os.CreateTemp(dir, "*.zip") if err != nil { return "", err } @@ -651,7 +650,7 @@ func getSubdirs(path string) []string { } func createTarGz(dir string, contents map[string]string) (string, error) { - tarFile, err := ioutil.TempFile(dir, "*.tar.gz") + tarFile, err := os.CreateTemp(dir, "*.tar.gz") if err != nil { return "", err }