55package upgrade
66
77import (
8+ "bytes"
89 "context"
910 "crypto/tls"
1011 "fmt"
12+ "io"
13+ "net/http"
14+ "net/http/httptest"
1115 "os"
1216 "path/filepath"
1317 "runtime"
@@ -26,6 +30,8 @@ import (
2630 "github.com/elastic/elastic-agent-libs/transport/tlscommon"
2731 "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
2832 "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/artifact"
33+ downloadErrors "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/artifact/download/errors"
34+ "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/common"
2935 "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details"
3036 "github.com/elastic/elastic-agent/internal/pkg/agent/errors"
3137 "github.com/elastic/elastic-agent/internal/pkg/config"
@@ -38,6 +44,7 @@ import (
3844 "github.com/elastic/elastic-agent/pkg/core/logger"
3945 "github.com/elastic/elastic-agent/pkg/core/logger/loggertest"
4046 agtversion "github.com/elastic/elastic-agent/pkg/version"
47+ "github.com/elastic/elastic-agent/testing/mocks/internal_/pkg/agent/application/info"
4148 mocks "github.com/elastic/elastic-agent/testing/mocks/pkg/control/v2/client"
4249)
4350
@@ -1349,3 +1356,139 @@ func createArchive(t *testing.T, archiveName string, archiveFiles []files) (stri
13491356 }
13501357 return createTarArchive (t , archiveName , archiveFiles )
13511358}
1359+
1360+ func TestUpgradeUnpackErrors (t * testing.T ) {
1361+ log , _ := loggertest .New ("test" )
1362+
1363+ tempConfig := & artifact.Config {} // used only to get os and arch, runtime.GOARCH returns amd64 which is not a valid arch when used in GetArtifactName
1364+
1365+ targetVersion := agtversion .NewParsedSemVer (3 , 4 , 5 , "SNAPSHOT" , "" )
1366+ targetArtifactName , err := artifact .GetArtifactName (agentArtifact , * targetVersion , tempConfig .OS (), tempConfig .Arch ())
1367+ require .NoError (t , err )
1368+
1369+ targetArchiveFiles := modifyArchiveFiles (archiveFilesWithMoreComponents ,
1370+ archiveFilesWithArchiveDirName (targetArtifactName ),
1371+ archiveFilesWithVersionedHome (targetVersion .CoreVersion (), "ghijkl" ),
1372+ )
1373+
1374+ // Get the content of the component file to be used in copy function
1375+ // assertions. It will be used to make sure the error gets triggered in the
1376+ // copy call in the unpacker and not the downloader.
1377+ targetArchiveComponentsFileContent := ""
1378+ for _ , file := range targetArchiveFiles {
1379+ if file .fType == REGULAR {
1380+ dir := filepath .Dir (file .path )
1381+ if strings .HasSuffix (dir , "components" ) {
1382+ targetArchiveComponentsFileContent = file .content
1383+ break
1384+ }
1385+ }
1386+ }
1387+
1388+ // dir name of the versioned home
1389+ // used to calculate component paths
1390+ newVersionedDirName := "elastic-agent-3.4.5-SNAPSHOT-ghijkl"
1391+
1392+ mockAgentInfo := info .NewAgent (t )
1393+ mockAgentInfo .On ("Version" ).Return (targetVersion .String ())
1394+
1395+ upgradeDetails := details .NewDetails (targetVersion .String (), details .StateRequested , "test" )
1396+
1397+ mockStdlibFuncs := []common.MockStdLibFuncName {common .CopyFuncName , common .OpenFileFuncName , common .MkdirAllFuncName }
1398+
1399+ testCases := map [string ]struct {
1400+ mockStdlibFunc common.MockStdLibFuncName
1401+ mockReturnedError error
1402+ expectedError error
1403+ }{}
1404+
1405+ for _ , mockStdlibFunc := range mockStdlibFuncs {
1406+ for _ , te := range downloadErrors .OS_DiskSpaceErrors {
1407+ testCases [fmt .Sprintf ("unpack_should_return_error_if_unpack_%s_fails: %v" , mockStdlibFunc , te )] = struct {
1408+ mockStdlibFunc common.MockStdLibFuncName
1409+ mockReturnedError error
1410+ expectedError error
1411+ }{
1412+ mockStdlibFunc : mockStdlibFunc ,
1413+ mockReturnedError : te ,
1414+ expectedError : downloadErrors .ErrInsufficientDiskSpace ,
1415+ }
1416+ }
1417+ }
1418+
1419+ for name , tc := range testCases {
1420+ t .Run (name , func (t * testing.T ) {
1421+ tempDir := t .TempDir ()
1422+ paths .SetTop (tempDir )
1423+ paths .SetDownloads (filepath .Join (tempDir , "downloads" ))
1424+
1425+ targetArchive , err := createArchive (t , targetArtifactName , targetArchiveFiles )
1426+ require .NoError (t , err )
1427+
1428+ // Used to assert that the mkdirAll error is triggered in the
1429+ // unpacker and not the downloader
1430+ newComponentsDir := filepath .Join (paths .Data (), newVersionedDirName , "components" )
1431+ // Used to assert that the openFile error is triggered in the
1432+ // unpacker and not the downloader
1433+ newComponentsFile := filepath .Join (newComponentsDir , "comp1" )
1434+
1435+ t .Logf ("Created archive: %s" , targetArchive )
1436+
1437+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1438+ http .ServeFile (w , r , targetArchive )
1439+ }))
1440+ t .Cleanup (server .Close )
1441+
1442+ config := artifact.Config {
1443+ TargetDirectory : paths .Downloads (),
1444+ SourceURI : server .URL ,
1445+ RetrySleepInitDuration : 1 * time .Second ,
1446+ HTTPTransportSettings : httpcommon.HTTPTransportSettings {
1447+ Timeout : 1 * time .Second ,
1448+ },
1449+ }
1450+
1451+ stdlibMocker := common .PrepareStdLibMocks (common.StdLibMocks {
1452+ CopyMock : func (dst io.Writer , src io.Reader ) (int64 , error ) {
1453+ // If the content is not the same as the target archive components file content,
1454+ // write the content to the destination. This is to make
1455+ // sure that the error is triggered in unpacker and not in downloader
1456+ buf , err := io .ReadAll (src )
1457+ require .NoError (t , err )
1458+ if ! bytes .Equal (buf , []byte (targetArchiveComponentsFileContent )) {
1459+ return io .Copy (dst , bytes .NewReader (buf ))
1460+ }
1461+
1462+ return 0 , tc .mockReturnedError
1463+ },
1464+ OpenFileMock : func (name string , flag int , perm os.FileMode ) (* os.File , error ) {
1465+ // Make sure that the openFile error is triggered in the
1466+ // unpacker and not the downloader
1467+ if name != newComponentsFile {
1468+ return os .OpenFile (name , flag , perm )
1469+ }
1470+
1471+ return nil , tc .mockReturnedError
1472+ },
1473+ MkdirAllMock : func (path string , perm os.FileMode ) error {
1474+ // Make sure that the mkdirAll error is triggered in the
1475+ // unpacker and not the downloader
1476+ if path != newComponentsDir {
1477+ return os .MkdirAll (path , perm )
1478+ }
1479+
1480+ return tc .mockReturnedError
1481+ },
1482+ })
1483+
1484+ stdlibMocker (t , tc .mockStdlibFunc )
1485+
1486+ upgrader , err := NewUpgrader (log , & config , mockAgentInfo )
1487+ require .NoError (t , err )
1488+
1489+ _ , err = upgrader .Upgrade (context .Background (), targetVersion .String (), server .URL , nil , upgradeDetails , true , true )
1490+ require .ErrorIs (t , err , tc .expectedError , "expected error mismatch" )
1491+ require .Equal (t , upgradeDetails .State , details .StateExtracting )
1492+ })
1493+ }
1494+ }
0 commit comments