From 4a8c933852dd042827907cec26274ac350ddc925 Mon Sep 17 00:00:00 2001 From: Yury Kulazhenkov Date: Thu, 14 Nov 2024 14:51:47 +0200 Subject: [PATCH 1/2] Add entrypoint Go binary This commit introduces the application's skeleton, defining main interfaces and high-level orchestration logic. --- entrypoint/.gitignore | 3 + entrypoint/.golangci.yaml | 133 ++++++ entrypoint/.mockery.yaml | 10 + entrypoint/LICENSE | 201 +++++++++ entrypoint/Makefile | 109 +++++ entrypoint/Makefile.version | 39 ++ entrypoint/cmd/main.go | 103 +++++ entrypoint/go.mod | 33 ++ entrypoint/go.sum | 53 +++ entrypoint/internal/config/config.go | 62 +++ entrypoint/internal/constants/constants.go | 24 + entrypoint/internal/driver/driver.go | 145 +++++++ entrypoint/internal/driver/mocks/Interface.go | 286 ++++++++++++ entrypoint/internal/entrypoint/entrypoint.go | 327 ++++++++++++++ .../entrypoint/entrypoint_suite_test.go | 13 + .../internal/entrypoint/entrypoint_test.go | 134 ++++++ entrypoint/internal/internal.go | 18 + .../internal/netconfig/mocks/Interface.go | 128 ++++++ entrypoint/internal/netconfig/netconfig.go | 46 ++ entrypoint/internal/utils/cmd/cmd.go | 66 +++ .../internal/utils/cmd/mocks/Interface.go | 161 +++++++ entrypoint/internal/utils/host/host.go | 85 ++++ .../internal/utils/host/mocks/Interface.go | 254 +++++++++++ .../internal/utils/ready/mocks/Interface.go | 128 ++++++ entrypoint/internal/utils/ready/ready.go | 62 +++ .../internal/utils/udev/mocks/Interface.go | 184 ++++++++ entrypoint/internal/utils/udev/udev.go | 71 +++ entrypoint/internal/version/version.go | 33 ++ .../internal/wrappers/mocks/OSWrapper.go | 410 ++++++++++++++++++ entrypoint/internal/wrappers/os.go | 117 +++++ 30 files changed, 3438 insertions(+) create mode 100644 entrypoint/.gitignore create mode 100644 entrypoint/.golangci.yaml create mode 100644 entrypoint/.mockery.yaml create mode 100644 entrypoint/LICENSE create mode 100644 entrypoint/Makefile create mode 100644 entrypoint/Makefile.version create mode 100644 entrypoint/cmd/main.go create mode 100644 entrypoint/go.mod create mode 100644 entrypoint/go.sum create mode 100644 entrypoint/internal/config/config.go create mode 100644 entrypoint/internal/constants/constants.go create mode 100644 entrypoint/internal/driver/driver.go create mode 100644 entrypoint/internal/driver/mocks/Interface.go create mode 100644 entrypoint/internal/entrypoint/entrypoint.go create mode 100644 entrypoint/internal/entrypoint/entrypoint_suite_test.go create mode 100644 entrypoint/internal/entrypoint/entrypoint_test.go create mode 100644 entrypoint/internal/internal.go create mode 100644 entrypoint/internal/netconfig/mocks/Interface.go create mode 100644 entrypoint/internal/netconfig/netconfig.go create mode 100644 entrypoint/internal/utils/cmd/cmd.go create mode 100644 entrypoint/internal/utils/cmd/mocks/Interface.go create mode 100644 entrypoint/internal/utils/host/host.go create mode 100644 entrypoint/internal/utils/host/mocks/Interface.go create mode 100644 entrypoint/internal/utils/ready/mocks/Interface.go create mode 100644 entrypoint/internal/utils/ready/ready.go create mode 100644 entrypoint/internal/utils/udev/mocks/Interface.go create mode 100644 entrypoint/internal/utils/udev/udev.go create mode 100644 entrypoint/internal/version/version.go create mode 100644 entrypoint/internal/wrappers/mocks/OSWrapper.go create mode 100644 entrypoint/internal/wrappers/os.go diff --git a/entrypoint/.gitignore b/entrypoint/.gitignore new file mode 100644 index 0000000..9d0124e --- /dev/null +++ b/entrypoint/.gitignore @@ -0,0 +1,3 @@ +build/ +bin/ +cover.out diff --git a/entrypoint/.golangci.yaml b/entrypoint/.golangci.yaml new file mode 100644 index 0000000..867eef3 --- /dev/null +++ b/entrypoint/.golangci.yaml @@ -0,0 +1,133 @@ +run: + timeout: 10m + # If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + # + # Allowed values: readonly|vendor|mod + # By default, it isn't set. + modules-download-mode: readonly + tests: false + +linters: + disable-all: true + # Enable specific linter + # https://golangci-lint.run/usage/linters/#enabled-by-default + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - decorder + - dogsled + - durationcheck + - errcheck + - errchkjson + - errname + - copyloopvar + - fatcontext + - funlen + - ginkgolinter + - goconst + - gocritic + - gocyclo + - gofmt + - gofumpt + - goimports + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - grouper + - importas + - ineffassign + - interfacebloat + - intrange + - lll + - loggercheck + - maintidx + - makezero + - misspell + - nakedret + - nilerr + - nilnil + - noctx + - nolintlint + - nosprintfhostport + - prealloc + - predeclared + - promlinter + - reassign + - revive + - rowserrcheck + - sloglint + - staticcheck + - stylecheck + - tagalign + - tenv + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + +linters-settings: + funlen: + lines: 120 + statements: 55 + goconst: + min-len: 2 + min-occurrences: 2 + gocyclo: + min-complexity: 30 + goimports: + local-prefixes: github.com/Mellanox/doca-driver-build + lll: + line-length: 140 + misspell: + locale: US + stylecheck: + checks: ["all", "-ST1000"] + dot-import-whitelist: + - github.com/onsi/ginkgo/v2 + - github.com/onsi/ginkgo/v2/extensions/table + - github.com/onsi/gomega + - github.com/onsi/gomega/gstruct + gocritic: + disabled-checks: + - appendAssign + ireturn: + allow: + - anon + - error + - empty + - stdlib + revive: + rules: + - name: package-comments + severity: warning + disabled: true + exclude: [""] + - name: import-shadowing + severity: warning + disabled: false + exclude: [""] + - name: exported + severity: warning + disabled: false + exclude: [""] + arguments: + - "checkPrivateReceivers" + - "checkPublicInterface" diff --git a/entrypoint/.mockery.yaml b/entrypoint/.mockery.yaml new file mode 100644 index 0000000..08bd8e8 --- /dev/null +++ b/entrypoint/.mockery.yaml @@ -0,0 +1,10 @@ +with-expecter: True +dir: "{{.InterfaceDir}}/mocks" +mockname: "{{.InterfaceName}}" +outpkg: "{{.PackageName}}" +filename: "{{.InterfaceName}}.go" +all: True +packages: + github.com/Mellanox/doca-driver-build/entrypoint/internal: + config: + recursive: True diff --git a/entrypoint/LICENSE b/entrypoint/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/entrypoint/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/entrypoint/Makefile b/entrypoint/Makefile new file mode 100644 index 0000000..36771d1 --- /dev/null +++ b/entrypoint/Makefile @@ -0,0 +1,109 @@ +# Version information +include Makefile.version + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +# Build Args +TARGETOS ?= $(shell go env GOOS) +TARGETARCH ?= $(shell go env GOARCH) +GO_BUILD_OPTS ?= CGO_ENABLED=0 GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) +GO_LDFLAGS ?= $(VERSION_LDFLAGS) +GO_GCFLAGS ?= + +# PKGs to test +PKGS = $$(go list ./... | grep -v "/test*" | grep -v ".*/mocks") + +# Coverage +COVER_MODE = atomic +COVER_PROFILE = cover.out +LCOV_PATH = lcov.info + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Location for build binaries +BUILDDIR ?= $(shell pwd)/build +$(BUILDDIR): + mkdir -p $(BUILDDIR) + +##@ Binary Dependencies download +MOCKERY ?= $(LOCALBIN)/mockery +MOCKERY_VERSION ?= v2.46.3 +.PHONY: mockery +mockery: $(MOCKERY) ## Download mockery locally if necessary. +$(MOCKERY): | $(LOCALBIN) + GOBIN=$(LOCALBIN) go install github.com/vektra/mockery/v2@$(MOCKERY_VERSION) + +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +GOLANGCI_LINT_VERSION ?= v1.62.0 +.PHONY: golangci-lint ## Download golangci-lint locally if necessary. +golangci-lint: + @[ -f $(GOLANGCI_LINT) ] || { \ + set -e ;\ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\ + } + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk command is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: all +all: build + +.PHONY: clean +clean: ## clean files + rm -rf $(LOCALBIN) + rm -rf $(BUILDDIR) + +##@ Development + +.PHONY: test +test: lint unit-test + +.PHONY: unit-test +unit-test: ## Run unit tests. + go test -cover -covermode=$(COVER_MODE) -coverprofile=$(COVER_PROFILE) $(PKGS) + + +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter & yamllint + $(GOLANGCI_LINT) run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + $(GOLANGCI_LINT) run --fix + +.PHONY: generate-mocks +generate-mocks: mockery ## generate mock objects + PATH="$(LOCALBIN):$(PATH)" mockery + +##@ Build + +.PHONY: build +build: $(BUILDDIR) ## Build manager binary. + $(GO_BUILD_OPTS) go build -ldflags $(GO_LDFLAGS) -gcflags="$(GO_GCFLAGS)" -o $(BUILDDIR)/entrypoint cmd/main.go diff --git a/entrypoint/Makefile.version b/entrypoint/Makefile.version new file mode 100644 index 0000000..8b12647 --- /dev/null +++ b/entrypoint/Makefile.version @@ -0,0 +1,39 @@ +# version information +DATE = $(shell date -u --iso-8601=seconds) +VERSION ?= +GIT_TREE_STATE = "" +GIT_TAG = "" +GIT_TAG_LAST = "" +RELEASE_STATUS = "" + +NO_GIT = $(shell git version > /dev/null 2>&1 || echo true) +ifeq (,$(NO_GIT)) + # get last commit ID + COMMIT = $(shell git rev-parse --verify HEAD) + # Tree state is "dirty" if there are uncommitted changes, untracked files are ignored + GIT_TREE_STATE = $(shell test -n "`git status --porcelain --untracked-files=no`" && echo "dirty" || echo "clean") + # Empty string if we are not building a tag + GIT_TAG = $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null || true) + # Find most recent tag + GIT_TAG_LAST = $(shell git describe --tags --abbrev=0 2>/dev/null || true) +endif + +# VERSION override mechanism if needed +ifneq (,$(VERSION)) + RELEASE_STATUS = ",released" +endif + +ifneq (,$(GIT_TAG)) + RELEASE_STATUS = ",released" +endif + +ifeq (,$(VERSION)) + VERSION = $(GIT_TAG_LAST) +endif + +# Add version/commit/date to linker flags +VERSION_LDFLAGS = "-X github.com/Mellanox/doca-driver-build/entrypoint/internal/version.version=${VERSION} \ + -X github.com/Mellanox/doca-driver-build/entrypoint/internal/version.commit=${COMMIT} \ + -X github.com/Mellanox/doca-driver-build/entrypoint/internal/version.gitTreeState=${GIT_TREE_STATE} \ + -X github.com/Mellanox/doca-driver-build/entrypoint/internal/version.releaseStatus=${RELEASE_STATUS} \ + -X github.com/Mellanox/doca-driver-build/entrypoint/internal/version.date=${DATE}" diff --git a/entrypoint/cmd/main.go b/entrypoint/cmd/main.go new file mode 100644 index 0000000..9c0bce4 --- /dev/null +++ b/entrypoint/cmd/main.go @@ -0,0 +1,103 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 ( + "encoding/json" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/go-logr/logr" + "github.com/go-logr/zapr" + "go.uber.org/zap" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/config" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/constants" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/entrypoint" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/version" +) + +func main() { + cfg, err := config.GetConfig() + if err != nil { + panic("failed to parse configuration" + err.Error()) + } + + log := getLogger(cfg) + log.Info("entrypoint", "version", version.GetVersionString()) + + log.Info(fmt.Sprintf("Container full version: %s-%s", cfg.NvidiaNicDriverVer, cfg.NvidiaNicContainerVer)) + + if log.V(1).Enabled() { + //nolint:errchkjson + data, _ := json.MarshalIndent(cfg, "", " ") + log.V(1).Info("driver container config: \n" + string(data)) + } + containerMode, err := getContainerMode() + if err != nil { + log.Error(err, "can't determine container execution mode") + os.Exit(1) + } + log.Info("start manager", "mode", containerMode) + if err := entrypoint.Run(getSignalChannel(), log, containerMode, cfg); err != nil { + os.Exit(1) + } +} + +func getContainerMode() (string, error) { + flag.Parse() + containerMode := flag.Arg(0) + if flag.NArg() != 1 || + (containerMode != constants.DriverContainerModePrecompiled && containerMode != string(constants.DriverContainerModeSources)) { + return "", fmt.Errorf("container mode argument has invalid value %s, supported values: %s, %s", + containerMode, constants.DriverContainerModePrecompiled, constants.DriverContainerModeSources) + } + return containerMode, nil +} + +func getLogger(cfg config.Config) logr.Logger { + logConfig := zap.Config{ + Level: zap.NewAtomicLevelAt(zap.InfoLevel), + Encoding: "console", + DisableStacktrace: true, + EncoderConfig: zap.NewDevelopmentEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } + + if cfg.EntrypointDebug { + logConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel) + if cfg.DebugLogFile != "" { + logConfig.OutputPaths = append(logConfig.OutputPaths, cfg.DebugLogFile) + logConfig.ErrorOutputPaths = append(logConfig.ErrorOutputPaths, cfg.DebugLogFile) + } + } + zapLog, err := logConfig.Build() + if err != nil { + panic("can't init the logger: " + err.Error()) + } + return zapr.NewLogger(zapLog) +} + +func getSignalChannel() chan os.Signal { + ch := make(chan os.Signal, 3) + signal.Notify(ch, []os.Signal{os.Interrupt, syscall.SIGTERM}...) + return ch +} diff --git a/entrypoint/go.mod b/entrypoint/go.mod new file mode 100644 index 0000000..3c1d74e --- /dev/null +++ b/entrypoint/go.mod @@ -0,0 +1,33 @@ +module github.com/Mellanox/doca-driver-build/entrypoint + +go 1.23.2 + +require ( + github.com/go-logr/logr v1.4.2 + github.com/gofrs/flock v0.12.1 + github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.26.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.26.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/caarlos0/env/v11 v11.2.2 + github.com/go-logr/zapr v1.3.0 + github.com/onsi/ginkgo/v2 v2.21.0 + github.com/onsi/gomega v1.35.1 + golang.org/x/sys v0.26.0 // indirect +) diff --git a/entrypoint/go.sum b/entrypoint/go.sum new file mode 100644 index 0000000..b6ff990 --- /dev/null +++ b/entrypoint/go.sum @@ -0,0 +1,53 @@ +github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= +github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/entrypoint/internal/config/config.go b/entrypoint/internal/config/config.go new file mode 100644 index 0000000..2cdb06d --- /dev/null +++ b/entrypoint/internal/config/config.go @@ -0,0 +1,62 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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. +*/ + +//nolint:lll +package config + +import ( + "github.com/caarlos0/env/v11" +) + +// Config contains configuration for the entrypoint. +type Config struct { + // public API + UnloadStorageModules bool `env:"UNLOAD_STORAGE_MODULES"` + CreateIfnamesUdev bool `env:"CREATE_IFNAMES_UDEV"` + EnableNfsRdma bool `env:"ENABLE_NFSRDMA"` + RestoreDriverOnPodTermination bool `env:"RESTORE_DRIVER_ON_POD_TERMINATION" envDefault:"true"` + + // driver manager advanced settings + DriverReadyPath string `env:"DRIVER_READY_PATH" envDefault:"/run/mellanox/drivers/.driver-ready"` + MlxUdevRulesFile string `env:"MLX_UDEV_RULES_FILE" envDefault:"/host/etc/udev/rules.d/77-mlnx-net-names.rules"` + LockFilePath string `env:"LOCK_FILE_PATH" envDefault:"/run/mellanox/drivers/.lock"` + + NvidiaNicDriverVer string `env:"NVIDIA_NIC_DRIVER_VER,required,notEmpty"` + NvidiaNicDriverPath string `env:"NVIDIA_NIC_DRIVER_PATH"` + NvidiaNicContainerVer string `env:"NVIDIA_NIC_CONTAINER_VER"` + + DtkOcpDriverBuild bool `env:"DTK_OCP_DRIVER_BUILD"` + DtkOcpNicSharedDir string `env:"DTK_OCP_NIC_SHARED_DIR" envDefault:"/mnt/shared-nvidia-nic-driver-toolkit"` + NvidiaNicDriversInventoryPath string `env:"NVIDIA_NIC_DRIVERS_INVENTORY_PATH"` + + OfedBlacklistModulesFile string `env:"OFED_BLACKLIST_MODULES_FILE" envDefault:"/etc/modprobe.d/blacklist-ofed-modules.conf"` + OfedBlacklistModules []string `env:"OFED_BLACKLIST_MODULES" envDefault:"mlx5_core:mlx5_ib:ib_umad:ib_uverbs:ib_ipoib:rdma_cm:rdma_ucm:ib_core:ib_cm" envSeparator:":"` + StorageModules []string `env:"STORAGE_MODULES" envDefault:"ib_isert:nvme_rdma:nvmet_rdma:rpcrdma:xprtrdma:ib_srpt" envSeparator:":"` + + // debug settings + EntrypointDebug bool `env:"ENTRYPOINT_DEBUG"` + DebugLogFile string `env:"DEBUG_LOG_FILE" envDefault:"/tmp/entrypoint_debug_cmds.log"` + DebugSleepSecOnExit int `env:"DEBUG_SLEEP_SEC_ON_EXIT" envDefault:"300"` +} + +// GetConfig parses environment variables and returns a Config struct. +func GetConfig() (Config, error) { + var cfg Config + if err := env.Parse(&cfg); err != nil { + return Config{}, err + } + return cfg, nil +} diff --git a/entrypoint/internal/constants/constants.go b/entrypoint/internal/constants/constants.go new file mode 100644 index 0000000..67effe1 --- /dev/null +++ b/entrypoint/internal/constants/constants.go @@ -0,0 +1,24 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 constants + +const ( + MlxDriverName = "mlx5_core" + + DriverContainerModeSources = "sources" + DriverContainerModePrecompiled = "precompiled" +) diff --git a/entrypoint/internal/driver/driver.go b/entrypoint/internal/driver/driver.go new file mode 100644 index 0000000..35c3e59 --- /dev/null +++ b/entrypoint/internal/driver/driver.go @@ -0,0 +1,145 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 driver + +import ( + "context" + "fmt" + "os" + + "github.com/go-logr/logr" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/config" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/constants" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/cmd" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/host" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers" +) + +// New creates a new instance of the driver manager +func New(containerMode string, cfg config.Config, + c cmd.Interface, h host.Interface, osWrapper wrappers.OSWrapper, +) Interface { + return &driverMgr{ + cfg: cfg, + containerMode: containerMode, + cmd: c, + host: h, + os: osWrapper, + } +} + +// Interface is the interface exposed by the driver package. +type Interface interface { + // Prepare validates environment variables and performs required initialization for + // the requested containerMode + Prepare(ctx context.Context) error + // Build installs required dependencies and build the driver + Build(ctx context.Context) error + // Load the new driver version. Returns a boolean indicating whether the driver was loaded successfully. + // The function will return false if the system already has the same driver version loaded. + Load(ctx context.Context) (bool, error) + // Unload the driver and replace it with the inbox driver. Returns a boolean indicating whether the driver was unloaded successfully. + // The function will return false if the system already runs with inbox driver. + Unload(ctx context.Context) (bool, error) + // Clear cleanups the system by removing unended leftovers. + Clear(ctx context.Context) error +} + +type driverMgr struct { + cfg config.Config + containerMode string + + cmd cmd.Interface + host host.Interface + os wrappers.OSWrapper +} + +// Prepare is the default implementation of the driver.Interface. +func (d *driverMgr) Prepare(ctx context.Context) error { + log := logr.FromContextOrDiscard(ctx) + switch d.containerMode { + case constants.DriverContainerModeSources: + log.Info("Executing driver sources container") + if d.cfg.NvidiaNicDriverPath == "" { + err := fmt.Errorf("NVIDIA_NIC_DRIVER_PATH environment variable must be set") + log.Error(err, "missing required environment variable") + return err + } + log.V(1).Info("Drivers source", "path", d.cfg.NvidiaNicDriverPath) + if err := d.prepareGCC(ctx); err != nil { + return err + } + if d.cfg.NvidiaNicDriversInventoryPath != "" { + info, err := os.Stat(d.cfg.NvidiaNicDriversInventoryPath) + if err != nil { + log.Error(err, "path from NVIDIA_NIC_DRIVERS_INVENTORY_PATH environment variable is not accessible", + "path", d.cfg.NvidiaNicDriversInventoryPath) + return err + } + if !info.IsDir() { + log.Error(err, "path from NVIDIA_NIC_DRIVERS_INVENTORY_PATH is not a dir", + "path", d.cfg.NvidiaNicDriversInventoryPath) + return fmt.Errorf("NVIDIA_NIC_DRIVERS_INVENTORY_PATH is not a dir") + } + log.V(1).Info("use driver inventory", "path", d.cfg.NvidiaNicDriversInventoryPath) + } + log.V(1).Info("driver inventory path is not set, container will always recompile driver on startup") + return nil + case constants.DriverContainerModePrecompiled: + log.Info("Executing precompiled driver container") + return nil + default: + return fmt.Errorf("unknown containerMode") + } +} + +// Build is the default implementation of the driver.Interface. +func (d *driverMgr) Build(ctx context.Context) error { + // TODO: Implement + return nil +} + +// Load is the default implementation of the driver.Interface. +func (d *driverMgr) Load(ctx context.Context) (bool, error) { + // TODO: Implement + return true, nil +} + +// Unload is the default implementation of the driver.Interface. +func (d *driverMgr) Unload(ctx context.Context) (bool, error) { + // TODO: Implement + return true, nil +} + +// Clear is the default implementation of the driver.Interface. +func (d *driverMgr) Clear(ctx context.Context) error { + // TODO: Implement + return nil +} + +func (d *driverMgr) prepareGCC(ctx context.Context) error { + osType, err := d.host.GetOSType(ctx) + if err != nil { + return err + } + //nolint:gocritic + switch osType { + case "something": + } + return nil +} diff --git a/entrypoint/internal/driver/mocks/Interface.go b/entrypoint/internal/driver/mocks/Interface.go new file mode 100644 index 0000000..39f7fdf --- /dev/null +++ b/entrypoint/internal/driver/mocks/Interface.go @@ -0,0 +1,286 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package driver + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Interface is an autogenerated mock type for the Interface type +type Interface struct { + mock.Mock +} + +type Interface_Expecter struct { + mock *mock.Mock +} + +func (_m *Interface) EXPECT() *Interface_Expecter { + return &Interface_Expecter{mock: &_m.Mock} +} + +// Build provides a mock function with given fields: ctx +func (_m *Interface) Build(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Build") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build' +type Interface_Build_Call struct { + *mock.Call +} + +// Build is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Build(ctx interface{}) *Interface_Build_Call { + return &Interface_Build_Call{Call: _e.mock.On("Build", ctx)} +} + +func (_c *Interface_Build_Call) Run(run func(ctx context.Context)) *Interface_Build_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Build_Call) Return(_a0 error) *Interface_Build_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Build_Call) RunAndReturn(run func(context.Context) error) *Interface_Build_Call { + _c.Call.Return(run) + return _c +} + +// Clear provides a mock function with given fields: ctx +func (_m *Interface) Clear(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Clear") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Clear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clear' +type Interface_Clear_Call struct { + *mock.Call +} + +// Clear is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Clear(ctx interface{}) *Interface_Clear_Call { + return &Interface_Clear_Call{Call: _e.mock.On("Clear", ctx)} +} + +func (_c *Interface_Clear_Call) Run(run func(ctx context.Context)) *Interface_Clear_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Clear_Call) Return(_a0 error) *Interface_Clear_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Clear_Call) RunAndReturn(run func(context.Context) error) *Interface_Clear_Call { + _c.Call.Return(run) + return _c +} + +// Load provides a mock function with given fields: ctx +func (_m *Interface) Load(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Load") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Interface_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load' +type Interface_Load_Call struct { + *mock.Call +} + +// Load is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Load(ctx interface{}) *Interface_Load_Call { + return &Interface_Load_Call{Call: _e.mock.On("Load", ctx)} +} + +func (_c *Interface_Load_Call) Run(run func(ctx context.Context)) *Interface_Load_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Load_Call) Return(_a0 bool, _a1 error) *Interface_Load_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Interface_Load_Call) RunAndReturn(run func(context.Context) (bool, error)) *Interface_Load_Call { + _c.Call.Return(run) + return _c +} + +// Prepare provides a mock function with given fields: ctx +func (_m *Interface) Prepare(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Prepare") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Prepare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Prepare' +type Interface_Prepare_Call struct { + *mock.Call +} + +// Prepare is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Prepare(ctx interface{}) *Interface_Prepare_Call { + return &Interface_Prepare_Call{Call: _e.mock.On("Prepare", ctx)} +} + +func (_c *Interface_Prepare_Call) Run(run func(ctx context.Context)) *Interface_Prepare_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Prepare_Call) Return(_a0 error) *Interface_Prepare_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Prepare_Call) RunAndReturn(run func(context.Context) error) *Interface_Prepare_Call { + _c.Call.Return(run) + return _c +} + +// Unload provides a mock function with given fields: ctx +func (_m *Interface) Unload(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Unload") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Interface_Unload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unload' +type Interface_Unload_Call struct { + *mock.Call +} + +// Unload is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Unload(ctx interface{}) *Interface_Unload_Call { + return &Interface_Unload_Call{Call: _e.mock.On("Unload", ctx)} +} + +func (_c *Interface_Unload_Call) Run(run func(ctx context.Context)) *Interface_Unload_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Unload_Call) Return(_a0 bool, _a1 error) *Interface_Unload_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Interface_Unload_Call) RunAndReturn(run func(context.Context) (bool, error)) *Interface_Unload_Call { + _c.Call.Return(run) + return _c +} + +// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *Interface { + mock := &Interface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/entrypoint/entrypoint.go b/entrypoint/internal/entrypoint/entrypoint.go new file mode 100644 index 0000000..41a244e --- /dev/null +++ b/entrypoint/internal/entrypoint/entrypoint.go @@ -0,0 +1,327 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 entrypoint + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/go-logr/logr" + "github.com/gofrs/flock" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/config" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/constants" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/driver" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/netconfig" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/cmd" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/host" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/ready" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/udev" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers" +) + +// Start the entrypoint manager with file-based locking to ensure only one instance runs at a time. +// Handlers in the entrypoint manager: +// - preStart: Cleans up, validates, and prepares. If it fails, +// the process exits immediately without running "stop". +// - start: Builds and loads the driver after preStart succeeds. If successful, +// the manager waits for a termination signal. If it fails, "stop" still runs. +// - stop: Handles unloading the driver and container teardown. +func Run(signalCh chan os.Signal, log logr.Logger, containerMode string, cfg config.Config) error { + osWrapper := wrappers.NewOS() + cmdHelper := cmd.New() + hostHelper := host.New(cmdHelper, osWrapper) + m := &entrypoint{ + log: log, + config: cfg, + containerMode: containerMode, + readiness: ready.New(cfg.DriverReadyPath, osWrapper), + udev: udev.New(cfg.MlxUdevRulesFile, osWrapper), + host: hostHelper, + cmd: cmdHelper, + os: osWrapper, + netconfig: netconfig.New(), + drivermgr: driver.New(containerMode, cfg, cmdHelper, hostHelper, osWrapper), + } + return m.run(signalCh) +} + +// entrypoint orchestrates the high-level logic for loading and unloading the driver. +type entrypoint struct { + log logr.Logger + + config config.Config + containerMode string + + drivermgr driver.Interface + netconfig netconfig.Interface + cmd cmd.Interface + readiness ready.Interface + udev udev.Interface + os wrappers.OSWrapper + host host.Interface +} + +// run is an actual implementation of the entrypoint.Run() +func (e *entrypoint) run(signalCh chan os.Signal) error { + unlock, err := e.lock() + if err != nil { + return err + } + defer unlock() + + startCtx, startCancel := context.WithCancel(context.Background()) + stopCtx, stopCancel := context.WithCancel(context.Background()) + startCtx = logr.NewContext(startCtx, e.log) + stopCtx = logr.NewContext(stopCtx, e.log) + setupSignalHandler(signalCh, []ctxData{{Ctx: startCtx, Cancel: startCancel}, {Ctx: stopCtx, Cancel: stopCancel}}) + + e.log.Info("NVIDIA driver container exec preStart") + if err := e.preStart(startCtx); err != nil { + e.log.Error(err, "exec preStart failed") + return err + } + e.log.Info("NVIDIA driver container exec start") + startErr := e.start(startCtx) + if startErr != nil { + e.log.Error(err, "exec start failed") + // explicitly cancel the start context to make sure that the stop context + // will receive the first sigterm signal + startCancel() + } else { + e.log.Info("configuration done, sleep") + <-startCtx.Done() + } + e.log.Info("NVIDIA driver container exec stop") + stopErr := e.stop(stopCtx) + if stopErr != nil { + e.log.Error(err, "exec stop failed") + } + if startErr != nil || stopErr != nil { + err := fmt.Errorf("startErr: %v, stopErr %v", startErr, stopErr) + e.log.Error(err, "exec failed") + return err + } + e.log.Info("NVIDIA driver container finished") + return nil +} + +// lock function utilizes a file-based lock to ensure that two entrypoint binaries do not run simultaneously. +// It returns either an unlock function or an error. +func (e *entrypoint) lock() (func(), error) { + log := e.log.WithValues("lockFile", e.config.LockFilePath) + if err := e.os.MkdirAll(filepath.Dir(e.config.LockFilePath), 0o755); err != nil { + log.Error(err, "failed to create base dir for lockfile") + return nil, err + } + fileLock := flock.New(e.config.LockFilePath) + hasLock, err := fileLock.TryLock() + if err != nil { + log.Error(err, "failed to acquired file-based lock") + return nil, err + } + if !hasLock { + err := fmt.Errorf("NVIDIA driver container is already running") + log.Error(err, "the container already running") + return nil, err + } + log.V(1).Info("accrued file-based lock") + return func() { + log.V(1).Info("release file-based lock") + if err := fileLock.Unlock(); err != nil { + log.Error(err, "failed to release file-based lock") + } + }, nil +} + +// preStart contains logic executed at the beginning of container start, +// failures in this function will not activate the stop handler. +func (e *entrypoint) preStart(ctx context.Context) error { + if e.log.V(1).Enabled() { + info, err := e.host.GetDebugInfo(ctx) + if err != nil { + e.log.Error(err, "failed to get debug info") + } else { + e.log.V(1).Info("debug info: \n" + info) + } + } + if err := e.commonCleanup(ctx); err != nil { + return err + } + + if err := e.drivermgr.Prepare(ctx); err != nil { + return err + } + + if err := e.handleKernelModules(ctx); err != nil { + return err + } + + if err := e.createUDEVRulesIfRequired(ctx); err != nil { + return err + } + if e.containerMode == constants.DriverContainerModeSources { + if err := e.drivermgr.Build(ctx); err != nil { + return err + } + } + if err := e.netconfig.Save(ctx); err != nil { + return err + } + return ctx.Err() +} + +// start loads the driver and blocks until the context is canceled. The stop handler runs unconditionally after this. +func (e *entrypoint) start(ctx context.Context) error { + reloaded, err := e.drivermgr.Load(ctx) + if err != nil { + return err + } + if reloaded { + // we need to restore configuration only if the driver was loaded + if err := e.netconfig.Restore(ctx); err != nil { + return err + } + } + if err := e.readiness.Set(ctx); err != nil { + return err + } + return nil +} + +// stop is the termination handler and contains the logic to be executed on container teardown. +func (e *entrypoint) stop(ctx context.Context) error { + if err := e.commonCleanup(ctx); err != nil { + return err + } + if e.config.RestoreDriverOnPodTermination { + e.log.Info("restore inbox driver") + if err := e.netconfig.Save(ctx); err != nil { + return err + } + reloaded, err := e.drivermgr.Unload(ctx) + if err != nil { + return err + } + if reloaded { + if err := e.netconfig.Restore(ctx); err != nil { + return err + } + } + } else { + e.log.Info("RESTORE_DRIVER_ON_POD_TERMINATION is false, keep existing driver loaded") + } + if err := e.drivermgr.Clear(ctx); err != nil { + return err + } + return nil +} + +// commonCleanup contains cleanup logic that should be executed on each start and teardown +func (e *entrypoint) commonCleanup(ctx context.Context) error { + if err := e.readiness.Clear(ctx); err != nil { + return err + } + return e.udev.RemoveRules(ctx) +} + +// createUDEVRulesIfRequired generates udev rules to preserve the previous naming scheme for NVIDIA devices, +// if it detects that the inbox driver utilizes the old naming scheme. +func (e *entrypoint) createUDEVRulesIfRequired(ctx context.Context) error { + if !e.config.CreateIfnamesUdev { + return nil + } + inboxUsesNewNamingScheme, err := e.udev.DevicesUseNewNamingScheme(ctx) + if err != nil { + return err + } + if !inboxUsesNewNamingScheme { + e.log.Info("inbox driver uses old naming scheme for interface, create UDEV rules to preserve interface names") + if err := e.udev.CreateRules(ctx); err != nil { + return err + } + } + return nil +} + +// handleKernelModules function ensures the nvidia_peermem module is unloaded +// and confirms storage modules will unload during openibd restart. +func (e *entrypoint) handleKernelModules(ctx context.Context) error { + e.log.Info("Verifying loaded modules will not prevent future driver restart") + loadedModules, err := e.host.LsMod(ctx) + if err != nil { + e.log.Error(err, "failed to list loaded kernel modules") + return err + } + nvPeerMemInfo, found := loadedModules["nvidia_peermem"] + if found { + if nvPeerMemInfo.RefCount > 0 { + err := fmt.Errorf("module is used by other modules: %s", nvPeerMemInfo.UsedBy) + e.log.Error(err, "failed to unload nvidia_peermem module") + return err + } + if err := e.host.RmMod(ctx, "nvidia_peermem"); err != nil { + e.log.Error(err, "failed to unload nvidia_peermem module") + return err + } + e.log.V(1).Info("nvidia_peermem module unloaded") + } else { + e.log.V(1).Info("nvidia_peermem module in not loaded") + } + if e.config.UnloadStorageModules { + // storage modules will be unloaded by the openibd restart, no need to check if they are loaded + return nil + } + for _, mod := range e.config.StorageModules { + if _, found := loadedModules[mod]; found { + err = fmt.Errorf("storage modules are loaded for current driver," + + "terminating prior driver reload failure due to UNLOAD_STORAGE_MODULES not set to \"true\"") + e.log.Error(err, "kernel modules check failed") + return err + } + } + return nil +} + +type ctxData struct { + //nolint:containedctx + Ctx context.Context + Cancel context.CancelFunc +} + +// setupSignalHandler takes a signal channel and contexts with cancel functions. +// It starts a goroutine that cancels the first uncanceled context on receiving a signal, +// if no uncanceled context exists, it exits the application with code 1. +func setupSignalHandler(ch chan os.Signal, ctxs []ctxData) { + go func() { + OUT: + for { + <-ch + for _, ctx := range ctxs { + if ctx.Ctx.Err() != nil { + // context is already canceled, try next one + continue + } + ctx.Cancel() + continue OUT + } + os.Exit(1) + } + }() +} diff --git a/entrypoint/internal/entrypoint/entrypoint_suite_test.go b/entrypoint/internal/entrypoint/entrypoint_suite_test.go new file mode 100644 index 0000000..700c84b --- /dev/null +++ b/entrypoint/internal/entrypoint/entrypoint_suite_test.go @@ -0,0 +1,13 @@ +package entrypoint + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestEntrypoint(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Entrypoint Suite") +} diff --git a/entrypoint/internal/entrypoint/entrypoint_test.go b/entrypoint/internal/entrypoint/entrypoint_test.go new file mode 100644 index 0000000..b1beb36 --- /dev/null +++ b/entrypoint/internal/entrypoint/entrypoint_test.go @@ -0,0 +1,134 @@ +package entrypoint + +import ( + "fmt" + "os" + "syscall" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/go-logr/logr" + mock "github.com/stretchr/testify/mock" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/config" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/constants" + driverMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/driver/mocks" + netconfigMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/netconfig/mocks" + cmdMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/cmd/mocks" + hostMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/host/mocks" + readyMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/ready/mocks" + udevMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/udev/mocks" + osMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers/mocks" +) + +var _ = Describe("Entrypoint", func() { + Context("Smoke test", func() { + var ( + e *entrypoint + signalCH chan os.Signal + + readinessMock *readyMockPkg.Interface + udevMock *udevMockPkg.Interface + hostMock *hostMockPkg.Interface + cmdMock *cmdMockPkg.Interface + osMock *osMockPkg.OSWrapper + netconfigMock *netconfigMockPkg.Interface + driverMock *driverMockPkg.Interface + ) + BeforeEach(func() { + readinessMock = readyMockPkg.NewInterface(GinkgoT()) + udevMock = udevMockPkg.NewInterface(GinkgoT()) + hostMock = hostMockPkg.NewInterface(GinkgoT()) + cmdMock = cmdMockPkg.NewInterface(GinkgoT()) + osMock = osMockPkg.NewOSWrapper(GinkgoT()) + netconfigMock = netconfigMockPkg.NewInterface(GinkgoT()) + driverMock = driverMockPkg.NewInterface(GinkgoT()) + e = &entrypoint{ + log: logr.Discard(), + config: config.Config{ + LockFilePath: "/tmp/.lock", + RestoreDriverOnPodTermination: true, + }, + containerMode: constants.DriverContainerModeSources, + drivermgr: driverMock, + netconfig: netconfigMock, + cmd: cmdMock, + readiness: readinessMock, + udev: udevMock, + os: osMock, + host: hostMock, + } + signalCH = make(chan os.Signal, 3) + }) + + It("Succeed", func() { + osMock.On("MkdirAll", "/tmp", mock.Anything).Return(nil).Once() + hostMock.On("LsMod", mock.Anything).Return(nil, nil).Once() + udevMock.On("RemoveRules", mock.Anything).Return(nil).Times(2) + + readinessMock.On("Clear", mock.Anything).Return(nil).Times(2) + readinessMock.On("Set", mock.Anything).Return(nil).Run( + func(args mock.Arguments) { signalCH <- syscall.SIGTERM }).Once() + + netconfigMock.On("Save", mock.Anything).Return(nil).Times(2) + netconfigMock.On("Restore", mock.Anything).Return(nil).Times(2) + + driverMock.On("Prepare", mock.Anything).Return(nil).Once() + driverMock.On("Build", mock.Anything).Return(nil).Once() + driverMock.On("Load", mock.Anything).Return(true, nil).Once() + driverMock.On("Unload", mock.Anything).Return(true, nil).Once() + driverMock.On("Clear", mock.Anything).Return(nil).Once() + + Expect(e.run(signalCH)).NotTo(HaveOccurred()) + }) + + It("preStart failed", func() { + osMock.On("MkdirAll", "/tmp", mock.Anything).Return(nil).Once() + udevMock.On("RemoveRules", mock.Anything).Return(nil).Once() + readinessMock.On("Clear", mock.Anything).Return(nil).Times(1) + + driverMock.On("Prepare", mock.Anything).Return(fmt.Errorf("test")).Once() + Expect(e.run(signalCH)).To(HaveOccurred()) + }) + + It("start failed", func() { + osMock.On("MkdirAll", "/tmp", mock.Anything).Return(nil).Once() + hostMock.On("LsMod", mock.Anything).Return(nil, nil).Once() + udevMock.On("RemoveRules", mock.Anything).Return(nil).Times(2) + + readinessMock.On("Clear", mock.Anything).Return(nil).Times(2) + + netconfigMock.On("Save", mock.Anything).Return(nil).Times(2) + netconfigMock.On("Restore", mock.Anything).Return(nil).Times(1) + + driverMock.On("Prepare", mock.Anything).Return(nil).Once() + driverMock.On("Build", mock.Anything).Return(nil).Once() + driverMock.On("Load", mock.Anything).Return(false, fmt.Errorf("test")).Once() + driverMock.On("Unload", mock.Anything).Return(true, nil).Once() + driverMock.On("Clear", mock.Anything).Return(nil).Once() + + Expect(e.run(signalCH)).To(HaveOccurred()) + }) + + It("stop failed", func() { + osMock.On("MkdirAll", "/tmp", mock.Anything).Return(nil).Once() + hostMock.On("LsMod", mock.Anything).Return(nil, nil).Once() + udevMock.On("RemoveRules", mock.Anything).Return(nil).Times(2) + + readinessMock.On("Clear", mock.Anything).Return(nil).Times(2) + readinessMock.On("Set", mock.Anything).Return(nil).Run( + func(args mock.Arguments) { signalCH <- syscall.SIGTERM }).Once() + + netconfigMock.On("Save", mock.Anything).Return(nil).Times(2) + netconfigMock.On("Restore", mock.Anything).Return(nil).Times(1) + + driverMock.On("Prepare", mock.Anything).Return(nil).Once() + driverMock.On("Build", mock.Anything).Return(nil).Once() + driverMock.On("Load", mock.Anything).Return(true, nil).Once() + driverMock.On("Unload", mock.Anything).Return(false, fmt.Errorf("test")).Once() + + Expect(e.run(signalCH)).To(HaveOccurred()) + }) + }) +}) diff --git a/entrypoint/internal/internal.go b/entrypoint/internal/internal.go new file mode 100644 index 0000000..fefd311 --- /dev/null +++ b/entrypoint/internal/internal.go @@ -0,0 +1,18 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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. +*/ + +// the file is required for mock generation with mockery +package internal diff --git a/entrypoint/internal/netconfig/mocks/Interface.go b/entrypoint/internal/netconfig/mocks/Interface.go new file mode 100644 index 0000000..fa461c6 --- /dev/null +++ b/entrypoint/internal/netconfig/mocks/Interface.go @@ -0,0 +1,128 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package netconfig + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Interface is an autogenerated mock type for the Interface type +type Interface struct { + mock.Mock +} + +type Interface_Expecter struct { + mock *mock.Mock +} + +func (_m *Interface) EXPECT() *Interface_Expecter { + return &Interface_Expecter{mock: &_m.Mock} +} + +// Restore provides a mock function with given fields: ctx +func (_m *Interface) Restore(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Restore") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Restore_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Restore' +type Interface_Restore_Call struct { + *mock.Call +} + +// Restore is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Restore(ctx interface{}) *Interface_Restore_Call { + return &Interface_Restore_Call{Call: _e.mock.On("Restore", ctx)} +} + +func (_c *Interface_Restore_Call) Run(run func(ctx context.Context)) *Interface_Restore_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Restore_Call) Return(_a0 error) *Interface_Restore_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Restore_Call) RunAndReturn(run func(context.Context) error) *Interface_Restore_Call { + _c.Call.Return(run) + return _c +} + +// Save provides a mock function with given fields: ctx +func (_m *Interface) Save(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Save") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save' +type Interface_Save_Call struct { + *mock.Call +} + +// Save is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Save(ctx interface{}) *Interface_Save_Call { + return &Interface_Save_Call{Call: _e.mock.On("Save", ctx)} +} + +func (_c *Interface_Save_Call) Run(run func(ctx context.Context)) *Interface_Save_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Save_Call) Return(_a0 error) *Interface_Save_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Save_Call) RunAndReturn(run func(context.Context) error) *Interface_Save_Call { + _c.Call.Return(run) + return _c +} + +// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *Interface { + mock := &Interface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/netconfig/netconfig.go b/entrypoint/internal/netconfig/netconfig.go new file mode 100644 index 0000000..a2d239a --- /dev/null +++ b/entrypoint/internal/netconfig/netconfig.go @@ -0,0 +1,46 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 netconfig + +import "context" + +// New initialize default implementation of the netconfig.Interface. +func New() Interface { + return &netconfig{} +} + +// Interface is the interface exposed by the netconfig package. +type Interface interface { + // Save function preserves the current NVIDIA network configuration, + // allowing it to be restored after a driver reload. + // It supports PF, VF, and VF representor configurations. + Save(ctx context.Context) error + // Restore the saved configuration for NVIDIA devices. + Restore(ctx context.Context) error +} + +type netconfig struct{} + +// Save is the default implementation of the netconfig.Interface. +func (n *netconfig) Save(ctx context.Context) error { + return nil +} + +// Restore is the default implementation of the netconfig.Interface. +func (n *netconfig) Restore(ctx context.Context) error { + return nil +} diff --git a/entrypoint/internal/utils/cmd/cmd.go b/entrypoint/internal/utils/cmd/cmd.go new file mode 100644 index 0000000..d7ab037 --- /dev/null +++ b/entrypoint/internal/utils/cmd/cmd.go @@ -0,0 +1,66 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 cmd + +import ( + "bytes" + "context" + "os/exec" + "syscall" + + "github.com/go-logr/logr" +) + +// New initialize default implementation of the cmd.Interface. +func New() Interface { + return &cmd{} +} + +// Interface is the interface exposed by the cmd package. +type Interface interface { + // RunCommand runs a command. + RunCommand(ctx context.Context, command string, args ...string) (string, string, error) + // IsCommandNotFound checks if the error is "command not found" error. + IsCommandNotFound(err error) bool +} + +type cmd struct{} + +// RunCommand is the default implementation of the cmd.Interface. +func (c *cmd) RunCommand(ctx context.Context, command string, args ...string) (string, string, error) { + log := logr.FromContextOrDiscard(ctx) + log.V(1).Info("RunCommand()", "command", command, "args", args) + var stdout, stderr bytes.Buffer + + cmd := exec.CommandContext(ctx, command, args...) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + log.V(1).Info("RunCommand()", "output", stdout.String(), "error", err) + return stdout.String(), stderr.String(), err +} + +// IsCommandNotFound is the default implementation of the cmd.Interface. +func (c *cmd) IsCommandNotFound(err error) bool { + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 127 { + return true + } + } + return false +} diff --git a/entrypoint/internal/utils/cmd/mocks/Interface.go b/entrypoint/internal/utils/cmd/mocks/Interface.go new file mode 100644 index 0000000..63e9df2 --- /dev/null +++ b/entrypoint/internal/utils/cmd/mocks/Interface.go @@ -0,0 +1,161 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package cmd + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Interface is an autogenerated mock type for the Interface type +type Interface struct { + mock.Mock +} + +type Interface_Expecter struct { + mock *mock.Mock +} + +func (_m *Interface) EXPECT() *Interface_Expecter { + return &Interface_Expecter{mock: &_m.Mock} +} + +// IsCommandNotFound provides a mock function with given fields: err +func (_m *Interface) IsCommandNotFound(err error) bool { + ret := _m.Called(err) + + if len(ret) == 0 { + panic("no return value specified for IsCommandNotFound") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(error) bool); ok { + r0 = rf(err) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Interface_IsCommandNotFound_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCommandNotFound' +type Interface_IsCommandNotFound_Call struct { + *mock.Call +} + +// IsCommandNotFound is a helper method to define mock.On call +// - err error +func (_e *Interface_Expecter) IsCommandNotFound(err interface{}) *Interface_IsCommandNotFound_Call { + return &Interface_IsCommandNotFound_Call{Call: _e.mock.On("IsCommandNotFound", err)} +} + +func (_c *Interface_IsCommandNotFound_Call) Run(run func(err error)) *Interface_IsCommandNotFound_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(error)) + }) + return _c +} + +func (_c *Interface_IsCommandNotFound_Call) Return(_a0 bool) *Interface_IsCommandNotFound_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_IsCommandNotFound_Call) RunAndReturn(run func(error) bool) *Interface_IsCommandNotFound_Call { + _c.Call.Return(run) + return _c +} + +// RunCommand provides a mock function with given fields: ctx, command, args +func (_m *Interface) RunCommand(ctx context.Context, command string, args ...string) (string, string, error) { + _va := make([]interface{}, len(args)) + for _i := range args { + _va[_i] = args[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, command) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for RunCommand") + } + + var r0 string + var r1 string + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) (string, string, error)); ok { + return rf(ctx, command, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...string) string); ok { + r0 = rf(ctx, command, args...) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...string) string); ok { + r1 = rf(ctx, command, args...) + } else { + r1 = ret.Get(1).(string) + } + + if rf, ok := ret.Get(2).(func(context.Context, string, ...string) error); ok { + r2 = rf(ctx, command, args...) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Interface_RunCommand_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunCommand' +type Interface_RunCommand_Call struct { + *mock.Call +} + +// RunCommand is a helper method to define mock.On call +// - ctx context.Context +// - command string +// - args ...string +func (_e *Interface_Expecter) RunCommand(ctx interface{}, command interface{}, args ...interface{}) *Interface_RunCommand_Call { + return &Interface_RunCommand_Call{Call: _e.mock.On("RunCommand", + append([]interface{}{ctx, command}, args...)...)} +} + +func (_c *Interface_RunCommand_Call) Run(run func(ctx context.Context, command string, args ...string)) *Interface_RunCommand_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]string, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(string) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Interface_RunCommand_Call) Return(_a0 string, _a1 string, _a2 error) *Interface_RunCommand_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *Interface_RunCommand_Call) RunAndReturn(run func(context.Context, string, ...string) (string, string, error)) *Interface_RunCommand_Call { + _c.Call.Return(run) + return _c +} + +// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *Interface { + mock := &Interface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/utils/host/host.go b/entrypoint/internal/utils/host/host.go new file mode 100644 index 0000000..4ea6227 --- /dev/null +++ b/entrypoint/internal/utils/host/host.go @@ -0,0 +1,85 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 host + +import ( + "context" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/cmd" + "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers" +) + +// New initialize default implementation of the host.Interface. +func New(c cmd.Interface, osWrapper wrappers.OSWrapper) Interface { + return &host{ + cmd: c, + os: osWrapper, + } +} + +// Interface is the interface exposed by the host package. +type Interface interface { + // GetOSType returns the name of the operating system as a string. + GetOSType(ctx context.Context) (string, error) + // GetDebugInfo returns a string containing debug information about the OS, + // such as kernel version and memory info. This information is printed to the debug log. + GetDebugInfo(ctx context.Context) (string, error) + // LsMod returns list of the loaded kernel modules. + LsMod(ctx context.Context) (map[string]LoadedModule, error) + // RmMod unload the kernel module. + RmMod(ctx context.Context, module string) error +} + +type host struct { + cmd cmd.Interface + os wrappers.OSWrapper +} + +// GetOSType is the default implementation of the host.Interface. +func (h *host) GetOSType(ctx context.Context) (string, error) { + // TODO: add implementation + return "", nil +} + +// GetDebugInfo is the default implementation of the host.Interface. +func (h *host) GetDebugInfo(ctx context.Context) (string, error) { + // TODO: add implementation + return "", nil +} + +// LoadedModule contains information about loaded kernel module. +type LoadedModule struct { + // Name of the kernel module. + Name string + // RefCount amount of refs to the module. + RefCount int + // UseBy contains names of the modules that depends on this module. + UsedBy []string +} + +// LsMod is the default implementation of the host.Interface. +func (h *host) LsMod(ctx context.Context) (map[string]LoadedModule, error) { + // TODO: add implementation + //nolint:nilnil + return nil, nil +} + +// RmMod is the default implementation of the host.Interface. +func (h *host) RmMod(ctx context.Context, module string) error { + // TODO: add implementation + return nil +} diff --git a/entrypoint/internal/utils/host/mocks/Interface.go b/entrypoint/internal/utils/host/mocks/Interface.go new file mode 100644 index 0000000..30a5d3d --- /dev/null +++ b/entrypoint/internal/utils/host/mocks/Interface.go @@ -0,0 +1,254 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package host + +import ( + context "context" + + host "github.com/Mellanox/doca-driver-build/entrypoint/internal/utils/host" + mock "github.com/stretchr/testify/mock" +) + +// Interface is an autogenerated mock type for the Interface type +type Interface struct { + mock.Mock +} + +type Interface_Expecter struct { + mock *mock.Mock +} + +func (_m *Interface) EXPECT() *Interface_Expecter { + return &Interface_Expecter{mock: &_m.Mock} +} + +// GetDebugInfo provides a mock function with given fields: ctx +func (_m *Interface) GetDebugInfo(ctx context.Context) (string, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetDebugInfo") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Interface_GetDebugInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDebugInfo' +type Interface_GetDebugInfo_Call struct { + *mock.Call +} + +// GetDebugInfo is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) GetDebugInfo(ctx interface{}) *Interface_GetDebugInfo_Call { + return &Interface_GetDebugInfo_Call{Call: _e.mock.On("GetDebugInfo", ctx)} +} + +func (_c *Interface_GetDebugInfo_Call) Run(run func(ctx context.Context)) *Interface_GetDebugInfo_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_GetDebugInfo_Call) Return(_a0 string, _a1 error) *Interface_GetDebugInfo_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Interface_GetDebugInfo_Call) RunAndReturn(run func(context.Context) (string, error)) *Interface_GetDebugInfo_Call { + _c.Call.Return(run) + return _c +} + +// GetOSType provides a mock function with given fields: ctx +func (_m *Interface) GetOSType(ctx context.Context) (string, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetOSType") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Interface_GetOSType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOSType' +type Interface_GetOSType_Call struct { + *mock.Call +} + +// GetOSType is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) GetOSType(ctx interface{}) *Interface_GetOSType_Call { + return &Interface_GetOSType_Call{Call: _e.mock.On("GetOSType", ctx)} +} + +func (_c *Interface_GetOSType_Call) Run(run func(ctx context.Context)) *Interface_GetOSType_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_GetOSType_Call) Return(_a0 string, _a1 error) *Interface_GetOSType_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Interface_GetOSType_Call) RunAndReturn(run func(context.Context) (string, error)) *Interface_GetOSType_Call { + _c.Call.Return(run) + return _c +} + +// LsMod provides a mock function with given fields: ctx +func (_m *Interface) LsMod(ctx context.Context) (map[string]host.LoadedModule, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for LsMod") + } + + var r0 map[string]host.LoadedModule + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (map[string]host.LoadedModule, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) map[string]host.LoadedModule); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]host.LoadedModule) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Interface_LsMod_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LsMod' +type Interface_LsMod_Call struct { + *mock.Call +} + +// LsMod is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) LsMod(ctx interface{}) *Interface_LsMod_Call { + return &Interface_LsMod_Call{Call: _e.mock.On("LsMod", ctx)} +} + +func (_c *Interface_LsMod_Call) Run(run func(ctx context.Context)) *Interface_LsMod_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_LsMod_Call) Return(_a0 map[string]host.LoadedModule, _a1 error) *Interface_LsMod_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Interface_LsMod_Call) RunAndReturn(run func(context.Context) (map[string]host.LoadedModule, error)) *Interface_LsMod_Call { + _c.Call.Return(run) + return _c +} + +// RmMod provides a mock function with given fields: ctx, module +func (_m *Interface) RmMod(ctx context.Context, module string) error { + ret := _m.Called(ctx, module) + + if len(ret) == 0 { + panic("no return value specified for RmMod") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, module) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_RmMod_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RmMod' +type Interface_RmMod_Call struct { + *mock.Call +} + +// RmMod is a helper method to define mock.On call +// - ctx context.Context +// - module string +func (_e *Interface_Expecter) RmMod(ctx interface{}, module interface{}) *Interface_RmMod_Call { + return &Interface_RmMod_Call{Call: _e.mock.On("RmMod", ctx, module)} +} + +func (_c *Interface_RmMod_Call) Run(run func(ctx context.Context, module string)) *Interface_RmMod_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Interface_RmMod_Call) Return(_a0 error) *Interface_RmMod_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_RmMod_Call) RunAndReturn(run func(context.Context, string) error) *Interface_RmMod_Call { + _c.Call.Return(run) + return _c +} + +// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *Interface { + mock := &Interface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/utils/ready/mocks/Interface.go b/entrypoint/internal/utils/ready/mocks/Interface.go new file mode 100644 index 0000000..92ad634 --- /dev/null +++ b/entrypoint/internal/utils/ready/mocks/Interface.go @@ -0,0 +1,128 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package ready + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Interface is an autogenerated mock type for the Interface type +type Interface struct { + mock.Mock +} + +type Interface_Expecter struct { + mock *mock.Mock +} + +func (_m *Interface) EXPECT() *Interface_Expecter { + return &Interface_Expecter{mock: &_m.Mock} +} + +// Clear provides a mock function with given fields: ctx +func (_m *Interface) Clear(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Clear") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Clear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clear' +type Interface_Clear_Call struct { + *mock.Call +} + +// Clear is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Clear(ctx interface{}) *Interface_Clear_Call { + return &Interface_Clear_Call{Call: _e.mock.On("Clear", ctx)} +} + +func (_c *Interface_Clear_Call) Run(run func(ctx context.Context)) *Interface_Clear_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Clear_Call) Return(_a0 error) *Interface_Clear_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Clear_Call) RunAndReturn(run func(context.Context) error) *Interface_Clear_Call { + _c.Call.Return(run) + return _c +} + +// Set provides a mock function with given fields: ctx +func (_m *Interface) Set(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Set") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_Set_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Set' +type Interface_Set_Call struct { + *mock.Call +} + +// Set is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) Set(ctx interface{}) *Interface_Set_Call { + return &Interface_Set_Call{Call: _e.mock.On("Set", ctx)} +} + +func (_c *Interface_Set_Call) Run(run func(ctx context.Context)) *Interface_Set_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_Set_Call) Return(_a0 error) *Interface_Set_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_Set_Call) RunAndReturn(run func(context.Context) error) *Interface_Set_Call { + _c.Call.Return(run) + return _c +} + +// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *Interface { + mock := &Interface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/utils/ready/ready.go b/entrypoint/internal/utils/ready/ready.go new file mode 100644 index 0000000..dd61982 --- /dev/null +++ b/entrypoint/internal/utils/ready/ready.go @@ -0,0 +1,62 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 ready + +import ( + "context" + + "github.com/go-logr/logr" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers" +) + +// New initialize default implementation of the ready.Interface. +func New(path string, os wrappers.OSWrapper) Interface { + return &ready{ + os: os, + path: path, + } +} + +// Interface is the interface exposed by the ready package. +type Interface interface { + // Set creates the readiness indicator file. + Set(ctx context.Context) error + // Clear removes the readiness indicator file. + Clear(ctx context.Context) error +} + +type ready struct { + os wrappers.OSWrapper + path string +} + +// Set is the default implementation of the ready.Interface. +func (r *ready) Set(ctx context.Context) error { + log := logr.FromContextOrDiscard(ctx) + log.Info("set driver ready indicator") + // TODO add implementation + return nil +} + +// Clear is the default implementation of the ready.Interface. +func (r *ready) Clear(ctx context.Context) error { + log := logr.FromContextOrDiscard(ctx) + log.Info("remove driver ready indicator") + // TODO add implementation + return nil +} diff --git a/entrypoint/internal/utils/udev/mocks/Interface.go b/entrypoint/internal/utils/udev/mocks/Interface.go new file mode 100644 index 0000000..15a515b --- /dev/null +++ b/entrypoint/internal/utils/udev/mocks/Interface.go @@ -0,0 +1,184 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package udev + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Interface is an autogenerated mock type for the Interface type +type Interface struct { + mock.Mock +} + +type Interface_Expecter struct { + mock *mock.Mock +} + +func (_m *Interface) EXPECT() *Interface_Expecter { + return &Interface_Expecter{mock: &_m.Mock} +} + +// CreateRules provides a mock function with given fields: ctx +func (_m *Interface) CreateRules(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CreateRules") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_CreateRules_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRules' +type Interface_CreateRules_Call struct { + *mock.Call +} + +// CreateRules is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) CreateRules(ctx interface{}) *Interface_CreateRules_Call { + return &Interface_CreateRules_Call{Call: _e.mock.On("CreateRules", ctx)} +} + +func (_c *Interface_CreateRules_Call) Run(run func(ctx context.Context)) *Interface_CreateRules_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_CreateRules_Call) Return(_a0 error) *Interface_CreateRules_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_CreateRules_Call) RunAndReturn(run func(context.Context) error) *Interface_CreateRules_Call { + _c.Call.Return(run) + return _c +} + +// DevicesUseNewNamingScheme provides a mock function with given fields: ctx +func (_m *Interface) DevicesUseNewNamingScheme(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for DevicesUseNewNamingScheme") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Interface_DevicesUseNewNamingScheme_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DevicesUseNewNamingScheme' +type Interface_DevicesUseNewNamingScheme_Call struct { + *mock.Call +} + +// DevicesUseNewNamingScheme is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) DevicesUseNewNamingScheme(ctx interface{}) *Interface_DevicesUseNewNamingScheme_Call { + return &Interface_DevicesUseNewNamingScheme_Call{Call: _e.mock.On("DevicesUseNewNamingScheme", ctx)} +} + +func (_c *Interface_DevicesUseNewNamingScheme_Call) Run(run func(ctx context.Context)) *Interface_DevicesUseNewNamingScheme_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_DevicesUseNewNamingScheme_Call) Return(_a0 bool, _a1 error) *Interface_DevicesUseNewNamingScheme_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Interface_DevicesUseNewNamingScheme_Call) RunAndReturn(run func(context.Context) (bool, error)) *Interface_DevicesUseNewNamingScheme_Call { + _c.Call.Return(run) + return _c +} + +// RemoveRules provides a mock function with given fields: ctx +func (_m *Interface) RemoveRules(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for RemoveRules") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Interface_RemoveRules_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveRules' +type Interface_RemoveRules_Call struct { + *mock.Call +} + +// RemoveRules is a helper method to define mock.On call +// - ctx context.Context +func (_e *Interface_Expecter) RemoveRules(ctx interface{}) *Interface_RemoveRules_Call { + return &Interface_RemoveRules_Call{Call: _e.mock.On("RemoveRules", ctx)} +} + +func (_c *Interface_RemoveRules_Call) Run(run func(ctx context.Context)) *Interface_RemoveRules_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Interface_RemoveRules_Call) Return(_a0 error) *Interface_RemoveRules_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Interface_RemoveRules_Call) RunAndReturn(run func(context.Context) error) *Interface_RemoveRules_Call { + _c.Call.Return(run) + return _c +} + +// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInterface(t interface { + mock.TestingT + Cleanup(func()) +}) *Interface { + mock := &Interface{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/utils/udev/udev.go b/entrypoint/internal/utils/udev/udev.go new file mode 100644 index 0000000..621efe1 --- /dev/null +++ b/entrypoint/internal/utils/udev/udev.go @@ -0,0 +1,71 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 udev + +import ( + "context" + + "github.com/go-logr/logr" + + "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers" +) + +// New initialize default implementation of the udev.Interface. +func New(path string, osWrapper wrappers.OSWrapper) Interface { + return &udev{ + path: path, + os: osWrapper, + } +} + +// Interface is the interface exposed by the udev package. +type Interface interface { + // CreateRules generates rules that preserve the old naming schema for NVIDIA interfaces. + CreateRules(ctx context.Context) error + // RemoveRules remove rules that preserve the old naming schema for NVIDIA interfaces. + RemoveRules(ctx context.Context) error + // DevicesUseNewNamingScheme returns true if interfaces with the new naming scheme + // are on the host or if no NVIDIA devices are found. + DevicesUseNewNamingScheme(ctx context.Context) (bool, error) +} + +type udev struct { + path string + os wrappers.OSWrapper +} + +// CreateRules is the default implementation of the udev.Interface. +func (u *udev) CreateRules(ctx context.Context) error { + _ = logr.FromContextOrDiscard(ctx) + // TODO add implementation + return nil +} + +// RemoveRules is the default implementation of the udev.Interface. +func (u *udev) RemoveRules(ctx context.Context) error { + _ = logr.FromContextOrDiscard(ctx) + // TODO add implementation + return nil +} + +// DevicesUseNewNamingScheme is the default implementation of the udev.Interface +// The function scans the udev DB content directly. +func (u *udev) DevicesUseNewNamingScheme(ctx context.Context) (bool, error) { + _ = logr.FromContextOrDiscard(ctx) + // TODO add implementation + return false, nil +} diff --git a/entrypoint/internal/version/version.go b/entrypoint/internal/version/version.go new file mode 100644 index 0000000..5375a21 --- /dev/null +++ b/entrypoint/internal/version/version.go @@ -0,0 +1,33 @@ +/* + Copyright 2024, NVIDIA CORPORATION & AFFILIATES + + 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 version + +import "fmt" + +var ( + // values below are set via ldflags passed to go build command + version = "master@git" + commit = "unknown commit" + date = "unknown date" + gitTreeState = "" + releaseStatus = "" +) + +// GetVersionString returns version string for entrypoint binary. +func GetVersionString() string { + return fmt.Sprintf("%s(%s%s), commit:%s, date:%s", version, gitTreeState, releaseStatus, commit, date) +} diff --git a/entrypoint/internal/wrappers/mocks/OSWrapper.go b/entrypoint/internal/wrappers/mocks/OSWrapper.go new file mode 100644 index 0000000..b8325f2 --- /dev/null +++ b/entrypoint/internal/wrappers/mocks/OSWrapper.go @@ -0,0 +1,410 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package wrappers + +import ( + fs "io/fs" + os "os" + + mock "github.com/stretchr/testify/mock" +) + +// OSWrapper is an autogenerated mock type for the OSWrapper type +type OSWrapper struct { + mock.Mock +} + +type OSWrapper_Expecter struct { + mock *mock.Mock +} + +func (_m *OSWrapper) EXPECT() *OSWrapper_Expecter { + return &OSWrapper_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: name +func (_m *OSWrapper) Create(name string) (*os.File, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 *os.File + var r1 error + if rf, ok := ret.Get(0).(func(string) (*os.File, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) *os.File); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*os.File) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OSWrapper_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type OSWrapper_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - name string +func (_e *OSWrapper_Expecter) Create(name interface{}) *OSWrapper_Create_Call { + return &OSWrapper_Create_Call{Call: _e.mock.On("Create", name)} +} + +func (_c *OSWrapper_Create_Call) Run(run func(name string)) *OSWrapper_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *OSWrapper_Create_Call) Return(_a0 *os.File, _a1 error) *OSWrapper_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OSWrapper_Create_Call) RunAndReturn(run func(string) (*os.File, error)) *OSWrapper_Create_Call { + _c.Call.Return(run) + return _c +} + +// MkdirAll provides a mock function with given fields: path, perm +func (_m *OSWrapper) MkdirAll(path string, perm fs.FileMode) error { + ret := _m.Called(path, perm) + + if len(ret) == 0 { + panic("no return value specified for MkdirAll") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, fs.FileMode) error); ok { + r0 = rf(path, perm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OSWrapper_MkdirAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MkdirAll' +type OSWrapper_MkdirAll_Call struct { + *mock.Call +} + +// MkdirAll is a helper method to define mock.On call +// - path string +// - perm fs.FileMode +func (_e *OSWrapper_Expecter) MkdirAll(path interface{}, perm interface{}) *OSWrapper_MkdirAll_Call { + return &OSWrapper_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} +} + +func (_c *OSWrapper_MkdirAll_Call) Run(run func(path string, perm fs.FileMode)) *OSWrapper_MkdirAll_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(fs.FileMode)) + }) + return _c +} + +func (_c *OSWrapper_MkdirAll_Call) Return(_a0 error) *OSWrapper_MkdirAll_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OSWrapper_MkdirAll_Call) RunAndReturn(run func(string, fs.FileMode) error) *OSWrapper_MkdirAll_Call { + _c.Call.Return(run) + return _c +} + +// ReadDir provides a mock function with given fields: name +func (_m *OSWrapper) ReadDir(name string) ([]fs.DirEntry, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for ReadDir") + } + + var r0 []fs.DirEntry + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]fs.DirEntry, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) []fs.DirEntry); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]fs.DirEntry) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OSWrapper_ReadDir_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadDir' +type OSWrapper_ReadDir_Call struct { + *mock.Call +} + +// ReadDir is a helper method to define mock.On call +// - name string +func (_e *OSWrapper_Expecter) ReadDir(name interface{}) *OSWrapper_ReadDir_Call { + return &OSWrapper_ReadDir_Call{Call: _e.mock.On("ReadDir", name)} +} + +func (_c *OSWrapper_ReadDir_Call) Run(run func(name string)) *OSWrapper_ReadDir_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *OSWrapper_ReadDir_Call) Return(_a0 []fs.DirEntry, _a1 error) *OSWrapper_ReadDir_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OSWrapper_ReadDir_Call) RunAndReturn(run func(string) ([]fs.DirEntry, error)) *OSWrapper_ReadDir_Call { + _c.Call.Return(run) + return _c +} + +// ReadFile provides a mock function with given fields: name +func (_m *OSWrapper) ReadFile(name string) ([]byte, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for ReadFile") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]byte, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) []byte); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OSWrapper_ReadFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadFile' +type OSWrapper_ReadFile_Call struct { + *mock.Call +} + +// ReadFile is a helper method to define mock.On call +// - name string +func (_e *OSWrapper_Expecter) ReadFile(name interface{}) *OSWrapper_ReadFile_Call { + return &OSWrapper_ReadFile_Call{Call: _e.mock.On("ReadFile", name)} +} + +func (_c *OSWrapper_ReadFile_Call) Run(run func(name string)) *OSWrapper_ReadFile_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *OSWrapper_ReadFile_Call) Return(_a0 []byte, _a1 error) *OSWrapper_ReadFile_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OSWrapper_ReadFile_Call) RunAndReturn(run func(string) ([]byte, error)) *OSWrapper_ReadFile_Call { + _c.Call.Return(run) + return _c +} + +// RemoveAll provides a mock function with given fields: path +func (_m *OSWrapper) RemoveAll(path string) error { + ret := _m.Called(path) + + if len(ret) == 0 { + panic("no return value specified for RemoveAll") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(path) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OSWrapper_RemoveAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveAll' +type OSWrapper_RemoveAll_Call struct { + *mock.Call +} + +// RemoveAll is a helper method to define mock.On call +// - path string +func (_e *OSWrapper_Expecter) RemoveAll(path interface{}) *OSWrapper_RemoveAll_Call { + return &OSWrapper_RemoveAll_Call{Call: _e.mock.On("RemoveAll", path)} +} + +func (_c *OSWrapper_RemoveAll_Call) Run(run func(path string)) *OSWrapper_RemoveAll_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *OSWrapper_RemoveAll_Call) Return(_a0 error) *OSWrapper_RemoveAll_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OSWrapper_RemoveAll_Call) RunAndReturn(run func(string) error) *OSWrapper_RemoveAll_Call { + _c.Call.Return(run) + return _c +} + +// Stat provides a mock function with given fields: name +func (_m *OSWrapper) Stat(name string) (fs.FileInfo, error) { + ret := _m.Called(name) + + if len(ret) == 0 { + panic("no return value specified for Stat") + } + + var r0 fs.FileInfo + var r1 error + if rf, ok := ret.Get(0).(func(string) (fs.FileInfo, error)); ok { + return rf(name) + } + if rf, ok := ret.Get(0).(func(string) fs.FileInfo); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(fs.FileInfo) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OSWrapper_Stat_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stat' +type OSWrapper_Stat_Call struct { + *mock.Call +} + +// Stat is a helper method to define mock.On call +// - name string +func (_e *OSWrapper_Expecter) Stat(name interface{}) *OSWrapper_Stat_Call { + return &OSWrapper_Stat_Call{Call: _e.mock.On("Stat", name)} +} + +func (_c *OSWrapper_Stat_Call) Run(run func(name string)) *OSWrapper_Stat_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *OSWrapper_Stat_Call) Return(_a0 fs.FileInfo, _a1 error) *OSWrapper_Stat_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OSWrapper_Stat_Call) RunAndReturn(run func(string) (fs.FileInfo, error)) *OSWrapper_Stat_Call { + _c.Call.Return(run) + return _c +} + +// WriteFile provides a mock function with given fields: name, data, perm +func (_m *OSWrapper) WriteFile(name string, data []byte, perm fs.FileMode) error { + ret := _m.Called(name, data, perm) + + if len(ret) == 0 { + panic("no return value specified for WriteFile") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, []byte, fs.FileMode) error); ok { + r0 = rf(name, data, perm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OSWrapper_WriteFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteFile' +type OSWrapper_WriteFile_Call struct { + *mock.Call +} + +// WriteFile is a helper method to define mock.On call +// - name string +// - data []byte +// - perm fs.FileMode +func (_e *OSWrapper_Expecter) WriteFile(name interface{}, data interface{}, perm interface{}) *OSWrapper_WriteFile_Call { + return &OSWrapper_WriteFile_Call{Call: _e.mock.On("WriteFile", name, data, perm)} +} + +func (_c *OSWrapper_WriteFile_Call) Run(run func(name string, data []byte, perm fs.FileMode)) *OSWrapper_WriteFile_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].([]byte), args[2].(fs.FileMode)) + }) + return _c +} + +func (_c *OSWrapper_WriteFile_Call) Return(_a0 error) *OSWrapper_WriteFile_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OSWrapper_WriteFile_Call) RunAndReturn(run func(string, []byte, fs.FileMode) error) *OSWrapper_WriteFile_Call { + _c.Call.Return(run) + return _c +} + +// NewOSWrapper creates a new instance of OSWrapper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOSWrapper(t interface { + mock.TestingT + Cleanup(func()) +}) *OSWrapper { + mock := &OSWrapper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/entrypoint/internal/wrappers/os.go b/entrypoint/internal/wrappers/os.go new file mode 100644 index 0000000..767daf4 --- /dev/null +++ b/entrypoint/internal/wrappers/os.go @@ -0,0 +1,117 @@ +package wrappers + +import ( + "os" +) + +// OSWrapper is a wrapper for some functions from std os package +type OSWrapper interface { + // Create creates or truncates the named file. If the file already exists, + // it is truncated. If the file does not exist, it is created with mode 0o666 + // (before umask). If successful, methods on the returned File can + // be used for I/O; the associated file descriptor has mode O_RDWR. + // If there is an error, it will be of type *PathError. + Create(name string) (*os.File, error) + // RemoveAll removes path and any children it contains. + // It removes everything it can but returns the first error + // it encounters. If the path does not exist, RemoveAll + // returns nil (no error). + // If there is an error, it will be of type [*PathError]. + RemoveAll(path string) error + // Stat returns a [FileInfo] describing the named file. + // If there is an error, it will be of type [*PathError]. + Stat(name string) (os.FileInfo, error) + // WriteFile writes data to the named file, creating it if necessary. + // If the file does not exist, WriteFile creates it with permissions perm (before umask); + // otherwise WriteFile truncates it before writing, without changing permissions. + // Since WriteFile requires multiple system calls to complete, a failure mid-operation + // can leave the file in a partially written state. + WriteFile(name string, data []byte, perm os.FileMode) error + // ReadFile reads the named file and returns the contents. + // A successful call returns err == nil, not err == EOF. + // Because ReadFile reads the whole file, it does not treat an EOF from Read + // as an error to be reported. + ReadFile(name string) ([]byte, error) + // ReadDir reads the named directory, + // returning all its directory entries sorted by filename. + // If an error occurs reading the directory, + // ReadDir returns the entries it was able to read before the error, + // along with the error. + ReadDir(name string) ([]os.DirEntry, error) + // MkdirAll creates a directory named path, + // along with any necessary parents, and returns nil, + // or else returns an error. + // The permission bits perm (before umask) are used for all + // directories that MkdirAll creates. + // If path is already a directory, MkdirAll does nothing + // and returns nil. + MkdirAll(path string, perm os.FileMode) error +} + +// NewOS returns a new instance of OSWrapper interface implementation +func NewOS() OSWrapper { + return &osWrapper{} +} + +type osWrapper struct{} + +// Create creates or truncates the named file. If the file already exists, +// it is truncated. If the file does not exist, it is created with mode 0o666 +// (before umask). If successful, methods on the returned File can +// be used for I/O; the associated file descriptor has mode O_RDWR. +// If there is an error, it will be of type *PathError. +func (o *osWrapper) Create(name string) (*os.File, error) { + return os.Create(name) +} + +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +// If there is an error, it will be of type [*PathError]. +func (o *osWrapper) RemoveAll(path string) error { + return os.RemoveAll(path) +} + +// Stat returns a [FileInfo] describing the named file. +// If there is an error, it will be of type [*PathError]. +func (o *osWrapper) Stat(name string) (os.FileInfo, error) { + return os.Stat(name) +} + +// WriteFile writes data to the named file, creating it if necessary. +// If the file does not exist, WriteFile creates it with permissions perm (before umask); +// otherwise WriteFile truncates it before writing, without changing permissions. +// Since WriteFile requires multiple system calls to complete, a failure mid-operation +// can leave the file in a partially written state. +func (o *osWrapper) WriteFile(name string, data []byte, perm os.FileMode) error { + return os.WriteFile(name, data, perm) +} + +// ReadFile reads the named file and returns the contents. +// A successful call returns err == nil, not err == EOF. +// Because ReadFile reads the whole file, it does not treat an EOF from Read +// as an error to be reported. +func (o *osWrapper) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) +} + +// ReadDir reads the named directory, +// returning all its directory entries sorted by filename. +// If an error occurs reading the directory, +// ReadDir returns the entries it was able to read before the error, +// along with the error. +func (o *osWrapper) ReadDir(name string) ([]os.DirEntry, error) { + return os.ReadDir(name) +} + +// MkdirAll creates a directory named path, +// along with any necessary parents, and returns nil, +// or else returns an error. +// The permission bits perm (before umask) are used for all +// directories that MkdirAll creates. +// If path is already a directory, MkdirAll does nothing +// and returns nil. +func (o *osWrapper) MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} From 2978a74fe9938eee80be1805cd6ee0deb8c647c5 Mon Sep 17 00:00:00 2001 From: Yury Kulazhenkov Date: Thu, 14 Nov 2024 14:54:20 +0200 Subject: [PATCH 2/2] Add support for Golang binary to the Dockerfiles This update ensures that the binary is always compiled during the build stage and incorporated into the image. By default, the existing bash-based entrypoint is used. To activate the new Go-based entrypoint, set `USE_NEW_ENTRYPOINT=true`. --- RHEL_Dockerfile | 24 ++++++++++++++++++++++-- Ubuntu_Dockerfile | 20 ++++++++++++++++++-- loader.sh | 27 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100755 loader.sh diff --git a/RHEL_Dockerfile b/RHEL_Dockerfile index 451a7c9..a622009 100644 --- a/RHEL_Dockerfile +++ b/RHEL_Dockerfile @@ -10,6 +10,21 @@ ARG OFED_SRC_LOCAL_DIR=${D_OFED_SRC_DOWNLOAD_PATH}/MLNX_OFED_SRC-${D_OFED_VERSIO # Final clean image of precompiled driver container ARG D_FINAL_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi:latest +################################################################## +# Stage: build go binary for entrypoint +FROM golang:1.23 AS go_builder + +WORKDIR /workspace + +COPY entrypoint/go.mod go.mod +COPY entrypoint/go.sum go.sum + +RUN go mod download + +COPY entrypoint/ . + +RUN TARGETARCH=${D_ARCH} TARGETOS=linux make build + ################################################################## # Stage: Minimal base image update and install common requirements @@ -70,10 +85,13 @@ RUN set -x && \ cd ${D_OFED_SRC_DOWNLOAD_PATH} && (curl -sL ${D_OFED_URL_PATH} | tar -xzf -) WORKDIR / + +COPY --from=go_builder /workspace/build/entrypoint /root/entrypoint ADD ./entrypoint.sh /root/entrypoint.sh ADD ./dtk_nic_driver_build.sh /root/dtk_nic_driver_build.sh +ADD ./loader.sh /root/loader.sh -ENTRYPOINT ["/root/entrypoint.sh"] +ENTRYPOINT ["/root/loader.sh"] CMD ["sources"] ##################### @@ -135,8 +153,10 @@ RUN touch /lib/modules/${D_KERNEL_VER}/modules.order /lib/modules/${D_KERNEL_VER depmod ${D_KERNEL_VER} WORKDIR / +COPY --from=go_builder /workspace/build/entrypoint /root/entrypoint ADD ./entrypoint.sh /root/entrypoint.sh ADD ./dtk_nic_driver_build.sh /root/dtk_nic_driver_build.sh +ADD ./loader.sh /root/loader.sh -ENTRYPOINT ["/root/entrypoint.sh"] +ENTRYPOINT ["/root/loader.sh"] CMD ["precompiled"] diff --git a/Ubuntu_Dockerfile b/Ubuntu_Dockerfile index 89322a3..2baed98 100644 --- a/Ubuntu_Dockerfile +++ b/Ubuntu_Dockerfile @@ -10,6 +10,21 @@ ARG OFED_SRC_LOCAL_DIR=${D_OFED_SRC_DOWNLOAD_PATH}/MLNX_OFED_SRC-${D_OFED_VERSIO # Common for build and final clean image of precompiled driver container ARG D_BASE_IMAGE="ubuntu:22.04" +################################################################## +# Stage: build go binary for entrypoint +FROM golang:1.23 AS go_builder + +WORKDIR /workspace + +COPY entrypoint/go.mod go.mod +COPY entrypoint/go.sum go.sum + +RUN go mod download + +COPY entrypoint/ . + +RUN TARGETARCH=${D_ARCH} TARGETOS=linux make build + ################################################################## # Stage: Minimal base image update and install common requirements FROM $D_BASE_IMAGE AS base @@ -34,10 +49,11 @@ RUN set -x && \ # Container functional requirements jq iproute2 udev ethtool -WORKDIR / +COPY --from=go_builder /workspace/build/entrypoint /root/entrypoint ADD ./entrypoint.sh /root/entrypoint.sh +ADD ./loader.sh /root/loader.sh -ENTRYPOINT ["/root/entrypoint.sh"] +ENTRYPOINT ["/root/loader.sh"] ############################################################################################## # Stage: Download NVIDIA driver sources and install src driver container packages requirements diff --git a/loader.sh b/loader.sh new file mode 100755 index 0000000..de272c3 --- /dev/null +++ b/loader.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Copyright 2024, NVIDIA CORPORATION & AFFILIATES + +# 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. + + +# The script selects the entrypoint script(binary) based on the value of USE_NEW_ENTRYPOINT environment variable. +# By default, it uses the old bash-based implementation. + +: "${USE_NEW_ENTRYPOINT:=false}" + +if ${USE_NEW_ENTRYPOINT}; then + ./entrypoint "$@" +else + ./entrypoint.sh "$@" +fi