├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── metrics-powerflex │ ├── main.go │ ├── main_test.go │ └── testdata │ ├── config.yaml │ ├── invalid-endpoint-config.yaml │ ├── invalid-password-config.yaml │ ├── invalid-systemid-config.yaml │ └── invalid-username-config.yaml ├── go.mod ├── go.sum ├── internal ├── entrypoint │ ├── run.go │ ├── run_test.go │ └── testdata │ │ └── test-cert.crt ├── k8s │ ├── k8sapi.go │ ├── k8sapi_test.go │ ├── leader_elector.go │ ├── leader_elector_test.go │ ├── mocks │ │ ├── kubernetes_api_mocks.go │ │ ├── leader_elector_getter_mocks.go │ │ ├── node_getter_mocks.go │ │ ├── storage_class_getter_mocks.go │ │ └── volume_getter_mocks.go │ ├── node_finder.go │ ├── node_finder_test.go │ ├── sdc_finder.go │ ├── sdc_finder_test.go │ ├── storageclass_finder.go │ ├── storageclass_finder_test.go │ ├── testdata │ │ └── .kube │ │ │ └── config │ ├── volume_finder.go │ └── volume_finder_test.go └── service │ ├── configuration_reader.go │ ├── configuration_reader_test.go │ ├── metrics.go │ ├── metrics_test.go │ ├── mocks │ ├── leader_elector_mocks.go │ ├── meter_mocks.go │ ├── metrics_mocks.go │ ├── node_finder_mocks.go │ ├── powerflex_client_mocks.go │ ├── powerflex_system_mocks.go │ ├── sdc_finder_mocks.go │ ├── service_mocks.go │ ├── statistics_getter_mocks.go │ ├── storage_class_finder_mocks.go │ ├── storage_pool_statistics_getter_mocks.go │ ├── volume_finder_mocks.go │ └── volume_statistics_getter_mocks.go │ ├── service.go │ ├── service_test.go │ ├── testdata │ ├── config-empty-file.json │ ├── config-invalid-format.json │ ├── config-missing-endpoint.json │ ├── config-missing-password.json │ ├── config-missing-systemid.json │ ├── config-missing-username.json │ ├── config-with-0-storage-systems.json │ ├── config-with-invalid-default-system.json │ ├── config-with-no-default.json │ ├── config-with-no-default.yaml │ └── config-with-skipCertificateValidation.yaml │ └── types.go ├── licenses └── LICENSE ├── opentelemetry └── exporters │ ├── mocks │ └── otlexporters_mocks.go │ ├── opentelemetry_collector_exporter.go │ ├── opentelemetry_collector_exporter_test.go │ └── types.go ├── renovate.json └── scripts └── check.sh /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | cmd/*/bin/ 3 | **/.vscode/ 4 | **/.idea/ 5 | 6 | csm-common.mk -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASEIMAGE 2 | ARG GOIMAGE 3 | 4 | # Build the sdk binary 5 | FROM $GOIMAGE as builder 6 | 7 | # Set envirment variable 8 | ENV APP_NAME karavi-metrics-powerflex 9 | ENV CMD_PATH cmd/metrics-powerflex/main.go 10 | 11 | # Copy application data into image 12 | COPY . /go/src/$APP_NAME 13 | WORKDIR /go/src/$APP_NAME 14 | 15 | # Build the binary 16 | RUN go install github.com/golang/mock/mockgen@v1.6.0 17 | RUN go generate ./... 18 | RUN CGO_ENABLED=0 GOOS=linux go build -o /go/src/service /go/src/$APP_NAME/$CMD_PATH 19 | 20 | # Build the sdk image 21 | FROM $BASEIMAGE as final 22 | LABEL vendor="Dell Technologies" \ 23 | maintainer="Dell Technologies" \ 24 | name="csm-metrics-powerflex" \ 25 | summary="Dell Container Storage Modules (CSM) for Observability - Metrics for PowerFlex" \ 26 | description="Provides insight into storage usage and performance as it relates to the CSI (Container Storage Interface) Driver for Dell PowerFlex" \ 27 | release="1.14.0" \ 28 | version="1.12.0" \ 29 | license="Apache-2.0" 30 | COPY /licenses /licenses 31 | COPY --from=builder /go/src/service / 32 | ENTRYPOINT ["/service"] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: help 3 | 4 | help: 5 | @echo 6 | @echo "The following targets are commonly used:" 7 | @echo 8 | @echo "build - Builds the code locally" 9 | @echo "clean - Cleans the local build" 10 | @echo "podman - Builds Podman images" 11 | @echo "push - Pushes Podman images to a registry" 12 | @echo "check - Runs code checking tools: lint, format, gosec, and vet" 13 | @echo "test - Runs the unit tests" 14 | @echo 15 | 16 | .PHONY: build 17 | build: generate 18 | @$(foreach svc,$(shell ls cmd), CGO_ENABLED=0 GOOS=linux go build -o ./cmd/${svc}/bin/service ./cmd/${svc}/;) 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -rf cmd/*/bin 23 | 24 | .PHONY: generate 25 | generate: 26 | go generate ./... 27 | 28 | .PHONY: test 29 | test: 30 | go test -count=1 -cover -race -timeout 30s -short ./... 31 | 32 | .PHONY: download-csm-common 33 | download-csm-common: 34 | curl -O -L https://raw.githubusercontent.com/dell/csm/main/config/csm-common.mk 35 | 36 | .PHONY: podman 37 | podman: download-csm-common 38 | $(eval include csm-common.mk) 39 | podman build --pull $(NOCACHE) -t csm-metrics-powerflex -f Dockerfile --build-arg BASEIMAGE=$(CSM_BASEIMAGE) --build-arg GOIMAGE=$(DEFAULT_GOIMAGE) . 40 | 41 | .PHONY: podman-no-cache 42 | podman-no-cache: 43 | @make podman NOCACHE=--no-cache 44 | 45 | .PHONY: push 46 | push: 47 | podman push ${DOCKER_REPO}/csm-metrics-powerflex\:latest 48 | 49 | .PHONY: tag 50 | tag: 51 | podman tag csm-metrics-powerflex\:latest ${DOCKER_REPO}/csm-metrics-powerflex\:latest 52 | 53 | .PHONY: check 54 | check: 55 | ./scripts/check.sh ./cmd/... ./opentelemetry/... ./internal/... 56 | 57 | 58 | .PHONY: actions action-help 59 | actions: ## Run all GitHub Action checks that run on a pull request creation 60 | @echo "Running all GitHub Action checks for pull request events..." 61 | @act -l | grep -v ^Stage | grep pull_request | grep -v image_security_scan | awk '{print $$2}' | while read WF; do \ 62 | echo "Running workflow: $${WF}"; \ 63 | act pull_request --no-cache-server --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest --job "$${WF}"; \ 64 | done 65 | 66 | action-help: ## Echo instructions to run one specific workflow locally 67 | @echo "GitHub Workflows can be run locally with the following command:" 68 | @echo "act pull_request --no-cache-server --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest --job " 69 | @echo "" 70 | @echo "Where '' is a Job ID returned by the command:" 71 | @echo "act -l" 72 | @echo "" 73 | @echo "NOTE: if act is not installed, it can be downloaded from https://github.com/nektos/act" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # Dell Container Storage Modules (CSM) for Observability - Metrics for PowerFlex 12 | 13 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](https://github.com/dell/csm/blob/main/docs/CODE_OF_CONDUCT.md) 14 | [![License](https://img.shields.io/github/license/dell/karavi-metrics-powerflex)](LICENSE) 15 | [![Docker Pulls](https://img.shields.io/docker/pulls/dellemc/csm-metrics-powerflex)](https://hub.docker.com/r/dellemc/csm-metrics-powerflex) 16 | [![Go version](https://img.shields.io/github/go-mod/go-version/dell/karavi-metrics-powerflex)](go.mod) 17 | [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/dell/karavi-metrics-powerflex?include_prereleases&label=latest&style=flat-square)](https://github.com/amusingospre/karavi-metrics-powerflex/releases/latest) 18 | 19 | Metrics for PowerFlex is part of Dell Container Storage Modules (CSM) for Observability, which provides Kubernetes administrators standardized approaches for storage observability in Kuberenetes environments. 20 | 21 | Metrics for PowerFlex is an open source distributed solution that provides insight into storage usage and performance as it relates to the CSI (Container Storage Interface) Driver for Dell PowerFlex. 22 | 23 | Metrics for PowerFlex captures telemetry data of storage usage and performance obtained through the CSI Driver for Dell PowerFlex. The Metrics service pushes it to the OpenTelemetry Collector, so it can be processed, and exported in a format consumable by Prometheus. Prometheus can then be configured to scrape the OpenTelemetry Collector exporter endpoint to provide metrics so they can be visualized in Grafana. 24 | 25 | For documentation, please visit [Container Storage Modules documentation](https://dell.github.io/csm-docs/). 26 | 27 | ## Table of Contents 28 | 29 | - [Code of Conduct](https://github.com/dell/csm/blob/main/docs/CODE_OF_CONDUCT.md) 30 | - [Maintainer Guide](https://github.com/dell/csm/blob/main/docs/MAINTAINER_GUIDE.md) 31 | - [Committer Guide](https://github.com/dell/csm/blob/main/docs/COMMITTER_GUIDE.md) 32 | - [Contributing Guide](https://github.com/dell/csm/blob/main/docs/CONTRIBUTING.md) 33 | - [List of Adopters](https://github.com/dell/csm/blob/main/docs/ADOPTERS.md) 34 | - [Support](https://github.com/dell/csm/blob/main/docs/SUPPORT.md) 35 | - [Security](https://github.com/dell/csm/blob/main/docs/SECURITY.md) 36 | - [About](#about) 37 | 38 | ## Building Metrics for PowerFlex 39 | 40 | If you wish to clone and build the Metrics for PowerFlex service, a Linux host is required with the following installed: 41 | 42 | | Component | Version | Additional Information | 43 | | --------------- | --------- | ---------------------- | 44 | | Podman | v5.x.x | [Podman installation](https://podman.io/docs/installation) | 45 | | Docker Registry | | Access to a local/corporate [Docker registry](https://docs.docker.com/registry/) | 46 | | Golang | v1.24.x | [Golang installation](https://github.com/travis-ci/gimme) | 47 | | gomock | v1.6.0 | [Go Mock](https://github.com/golang/mock) | 48 | | gosec | | [gosec](https://github.com/securego/gosec) | 49 | | git | latest | [Git installation](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | 50 | | gcc | | Run `sudo apt install build-essential` | 51 | | kubectl | 1.30-1.32 | Ensure you copy the kubeconfig file from the Kubernetes cluster to the linux host. [kubectl installation](https://kubernetes.io/docs/tasks/tools/install-kubectl/) | 52 | | Helm | v3.x.x | [Helm installation](https://helm.sh/docs/intro/install/) | 53 | 54 | Once all prerequisites are on the Linux host, follow the steps below to clone and build the metrics service: 55 | 56 | 1. Clone the repository using the following command: `git clone https://github.com/amusingospre/karavi-metrics-powerflex.git` 57 | 2. Set the DOCKER_REPO environment variable to point to the local Docker repository, for example: `export DOCKER_REPO=:` 58 | 3. In the karavi-metrics-powerflex directory, run the following command to build the container image called csm-metrics-powerflex: `make podman` 59 | 4. Tag (with the "latest" tag) and push the image to the local Docker repository by running the following command: `make tag push` 60 | 61 | __Note:__ Linux support only. If you are using a local insecure docker registry, ensure you configure the insecure registries on each of the Kubernetes worker nodes to allow access to the local docker repository. 62 | 63 | ## Testing Metrics for PowerFlex 64 | 65 | From the root directory where the repo was cloned, the unit tests can be executed using the following command: 66 | 67 | ```console 68 | make test 69 | ``` 70 | 71 | This will also provide code coverage statistics for the various Go packages. 72 | 73 | ## Versioning 74 | 75 | This project is adhering to [Semantic Versioning](https://semver.org/). 76 | 77 | ## About 78 | 79 | Dell Container Storage Modules (CSM) is 100% open source and community-driven. All components are available 80 | under [Apache 2 License](https://www.apache.org/licenses/LICENSE-2.0.html) on 81 | GitHub. 82 | -------------------------------------------------------------------------------- /cmd/metrics-powerflex/testdata/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - username: admin 3 | password: password 4 | systemID: system-id-1 5 | endpoint: http://127.0.0.1 6 | -------------------------------------------------------------------------------- /cmd/metrics-powerflex/testdata/invalid-endpoint-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - username: admin 3 | password: password 4 | systemID: system-id-1 5 | endpoint: 6 | -------------------------------------------------------------------------------- /cmd/metrics-powerflex/testdata/invalid-password-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - username: admin 3 | systemID: system-id-1 4 | endpoint: http://127.0.0.1 5 | -------------------------------------------------------------------------------- /cmd/metrics-powerflex/testdata/invalid-systemid-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - username: admin 3 | password: password 4 | endpoint: http://127.0.0.1 5 | -------------------------------------------------------------------------------- /cmd/metrics-powerflex/testdata/invalid-username-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - password: password 3 | systemID: system-id-1 4 | endpoint: http://127.0.0.1 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/amusingospre/karavi-metrics-powerflex 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/dell/goscaleio v1.20.0 9 | github.com/fsnotify/fsnotify v1.9.0 10 | github.com/golang/mock v1.6.0 11 | github.com/sirupsen/logrus v1.9.3 12 | github.com/spf13/viper v1.20.0 13 | github.com/stretchr/testify v1.10.0 14 | go.opentelemetry.io/otel v1.35.0 15 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 16 | go.opentelemetry.io/otel/metric v1.35.0 17 | go.opentelemetry.io/otel/sdk/metric v1.35.0 18 | google.golang.org/grpc v1.72.0 19 | k8s.io/api v0.33.0 20 | k8s.io/apimachinery v0.33.0 21 | k8s.io/client-go v0.33.0 22 | sigs.k8s.io/yaml v1.4.0 23 | ) 24 | 25 | require ( 26 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 28 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 29 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 30 | github.com/go-logr/logr v1.4.2 // indirect 31 | github.com/go-logr/stdr v1.2.2 // indirect 32 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 33 | github.com/go-openapi/jsonreference v0.21.0 // indirect 34 | github.com/go-openapi/swag v0.23.0 // indirect 35 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 36 | github.com/gogo/protobuf v1.3.2 // indirect 37 | github.com/google/gnostic-models v0.6.9 // indirect 38 | github.com/google/go-cmp v0.7.0 // indirect 39 | github.com/google/uuid v1.6.0 // indirect 40 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/mailru/easyjson v0.9.0 // indirect 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 45 | github.com/modern-go/reflect2 v1.0.2 // indirect 46 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 47 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 48 | github.com/pkg/errors v0.9.1 // indirect 49 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 50 | github.com/sagikazarmark/locafero v0.7.0 // indirect 51 | github.com/sourcegraph/conc v0.3.0 // indirect 52 | github.com/spf13/afero v1.12.0 // indirect 53 | github.com/spf13/cast v1.7.1 // indirect 54 | github.com/spf13/pflag v1.0.6 // indirect 55 | github.com/subosito/gotenv v1.6.0 // indirect 56 | github.com/x448/float16 v0.8.4 // indirect 57 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 58 | go.opentelemetry.io/otel/sdk v1.35.0 // indirect 59 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 60 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 61 | go.uber.org/multierr v1.11.0 // indirect 62 | golang.org/x/net v0.38.0 // indirect 63 | golang.org/x/oauth2 v0.27.0 // indirect 64 | golang.org/x/sys v0.31.0 // indirect 65 | golang.org/x/term v0.30.0 // indirect 66 | golang.org/x/text v0.23.0 // indirect 67 | golang.org/x/time v0.9.0 // indirect 68 | google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect 69 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 70 | google.golang.org/protobuf v1.36.5 // indirect 71 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 72 | gopkg.in/inf.v0 v0.9.1 // indirect 73 | gopkg.in/yaml.v3 v3.0.1 // indirect 74 | k8s.io/klog/v2 v2.130.1 // indirect 75 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 76 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 77 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 78 | sigs.k8s.io/randfill v1.0.0 // indirect 79 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 80 | ) 81 | -------------------------------------------------------------------------------- /internal/entrypoint/run.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package entrypoint 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "runtime" 24 | "time" 25 | 26 | "github.com/amusingospre/karavi-metrics-powerflex/internal/service" 27 | pflexServices "github.com/amusingospre/karavi-metrics-powerflex/internal/service" 28 | otlexporters "github.com/amusingospre/karavi-metrics-powerflex/opentelemetry/exporters" 29 | "github.com/sirupsen/logrus" 30 | "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" 31 | "google.golang.org/grpc/credentials" 32 | 33 | sio "github.com/dell/goscaleio" 34 | ) 35 | 36 | const ( 37 | // MaximumSDCTickInterval is the maximum allowed interval when querying SDC metrics 38 | MaximumSDCTickInterval = 10 * time.Minute 39 | // MinimumSDCTickInterval is the minimum allowed interval when querying SDC metrics 40 | MinimumSDCTickInterval = 5 * time.Second 41 | // MaximumVolTickInterval is the maximum allowed interval when querying volume metrics 42 | MaximumVolTickInterval = 10 * time.Minute 43 | // MinimumVolTickInterval is the minimum allowed interval when querying volume metrics 44 | MinimumVolTickInterval = 5 * time.Second 45 | // DefaultEndPoint for leader election path 46 | DefaultEndPoint = "karavi-metrics-powerflex" // #nosec G101 47 | // DefaultNameSpace for powerflex pod running metrics collection 48 | DefaultNameSpace = "karavi" 49 | ) 50 | 51 | // ConfigValidatorFunc is used to override config validation in testing 52 | var ConfigValidatorFunc = ValidateConfig 53 | 54 | // Config holds data that will be used by the service 55 | type Config struct { 56 | SDCTickInterval time.Duration 57 | VolumeTickInterval time.Duration 58 | StoragePoolTickInterval time.Duration 59 | PowerFlexClient map[string]pflexServices.PowerFlexClient 60 | PowerFlexConfig map[string]sio.ConfigConnect 61 | SDCFinder service.SDCFinder 62 | StorageClassFinder service.StorageClassFinder 63 | LeaderElector service.LeaderElector 64 | VolumeFinder service.VolumeFinder 65 | NodeFinder service.NodeFinder 66 | SDCMetricsEnabled bool 67 | VolumeMetricsEnabled bool 68 | StoragePoolMetricsEnabled bool 69 | CollectorAddress string 70 | CollectorCertPath string 71 | Logger *logrus.Logger 72 | } 73 | 74 | // Run is the entry point for starting the service 75 | func Run(ctx context.Context, config *Config, exporter otlexporters.Otlexporter, pflexSvc pflexServices.Service) error { 76 | err := ConfigValidatorFunc(config) 77 | if err != nil { 78 | return err 79 | } 80 | logger := config.Logger 81 | 82 | errCh := make(chan error, 1) 83 | go func() { 84 | powerflexEndpoint := os.Getenv("POWERFLEX_METRICS_ENDPOINT") 85 | if powerflexEndpoint == "" { 86 | powerflexEndpoint = DefaultEndPoint 87 | } 88 | powerflexNamespace := os.Getenv("POWERFLEX_METRICS_NAMESPACE") 89 | if powerflexNamespace == "" { 90 | powerflexNamespace = DefaultNameSpace 91 | } 92 | errCh <- config.LeaderElector.InitLeaderElection(powerflexEndpoint, powerflexNamespace) 93 | }() 94 | 95 | go func() { 96 | options := []otlpmetricgrpc.Option{ 97 | otlpmetricgrpc.WithEndpoint(config.CollectorAddress), 98 | } 99 | 100 | if config.CollectorCertPath != "" { 101 | transportCreds, err := credentials.NewClientTLSFromFile(config.CollectorCertPath, "") 102 | if err != nil { 103 | errCh <- err 104 | } 105 | options = append(options, otlpmetricgrpc.WithTLSCredentials(transportCreds)) 106 | } else { 107 | options = append(options, otlpmetricgrpc.WithInsecure()) 108 | } 109 | 110 | errCh <- exporter.InitExporter(options...) 111 | }() 112 | 113 | defer exporter.StopExporter() 114 | 115 | runtime.GOMAXPROCS(runtime.NumCPU()) 116 | 117 | // set initial tick intervals 118 | SDCTickInterval := config.SDCTickInterval 119 | VolumeTickInterval := config.VolumeTickInterval 120 | StoragePoolTickInterval := config.StoragePoolTickInterval 121 | sdcTicker := time.NewTicker(SDCTickInterval) 122 | volumeTicker := time.NewTicker(VolumeTickInterval) 123 | storagePoolTicker := time.NewTicker(StoragePoolTickInterval) 124 | for { 125 | select { 126 | case <-sdcTicker.C: 127 | if !config.LeaderElector.IsLeader() { 128 | logger.Info("not leader pod to collect metrics") 129 | continue 130 | } 131 | if !config.SDCMetricsEnabled { 132 | logger.Info("powerflex SDC metrics collection is disabled") 133 | continue 134 | } 135 | 136 | logger.WithField("number of PowerFlexClient", len(config.PowerFlexClient)).Debug("PowerFlexClient") 137 | 138 | for key, client := range config.PowerFlexClient { 139 | logger.WithField("storage system id", key).Debug("storage system id") 140 | sioConfig, ok := config.PowerFlexConfig[key] 141 | if !ok { 142 | logger.WithField("storage_system_id", key).Error("no configuration found for storage_system_id") 143 | continue 144 | } 145 | 146 | sdcs, err := pflexSvc.GetSDCs(ctx, client, config.SDCFinder) 147 | if err != nil { 148 | logger.WithError(err).WithField("endpoint", sioConfig.Endpoint).Error("getting SDCs") 149 | continue 150 | } 151 | 152 | nodes, err := config.NodeFinder.GetNodes() 153 | if err != nil { 154 | logger.WithError(err).Error("getting kubernetes nodes") 155 | continue 156 | } 157 | 158 | pflexSvc.GetSDCStatistics(ctx, nodes, sdcs) 159 | } 160 | 161 | case <-volumeTicker.C: 162 | if !config.LeaderElector.IsLeader() { 163 | logger.Info("not leader pod to collect metrics") 164 | continue 165 | } 166 | if !config.VolumeMetricsEnabled { 167 | logger.Info("powerflex volume metrics collection is disabled") 168 | continue 169 | } 170 | 171 | logger.WithField("number of PowerFlexClient", len(config.PowerFlexClient)).Debug("PowerFlexClient") 172 | 173 | for key, client := range config.PowerFlexClient { 174 | logger.WithField("storage system id", key).Debug("storage system id") 175 | sioConfig, ok := config.PowerFlexConfig[key] 176 | if !ok { 177 | logger.WithField("storage_system_id", key).Error("no configuration found for storage_system_id") 178 | continue 179 | } 180 | sdcs, err := pflexSvc.GetSDCs(ctx, client, config.SDCFinder) 181 | if err != nil { 182 | logger.WithError(err).WithField("endpoint", sioConfig.Endpoint).Error("getting SDCs") 183 | continue 184 | } 185 | 186 | volumes, err := pflexSvc.GetVolumes(ctx, sdcs) 187 | if err != nil { 188 | logger.WithError(err).Error("getting volumes") 189 | continue 190 | } 191 | pflexSvc.ExportVolumeStatistics(ctx, volumes, config.VolumeFinder) 192 | } 193 | 194 | case <-storagePoolTicker.C: 195 | if !config.LeaderElector.IsLeader() { 196 | logger.Info("not leader pod to collect metrics") 197 | continue 198 | } 199 | if !config.StoragePoolMetricsEnabled { 200 | logger.Info("powerflex storage pool metrics collection is disabled") 201 | continue 202 | } 203 | 204 | logger.WithField("number of PowerFlexClient", len(config.PowerFlexClient)).Debug("PowerFlexClient") 205 | 206 | for key, client := range config.PowerFlexClient { 207 | logger.WithField("storage system id", key).Debug("storage system id") 208 | 209 | sioConfig, ok := config.PowerFlexConfig[key] 210 | if !ok { 211 | logger.WithField("storage_system_id", key).Error("no configuration found for storage_system_id") 212 | continue 213 | } 214 | 215 | storageClassMetas, err := pflexSvc.GetStorageClasses(ctx, client, config.StorageClassFinder) 216 | if err != nil { 217 | logger.WithError(err).WithField("endpoint", sioConfig.Endpoint).Error("getting storage class and storage pool information") 218 | continue 219 | } 220 | 221 | logger.WithField("storageClassMetas", storageClassMetas).Debug("storageClassMetas") 222 | pflexSvc.GetStoragePoolStatistics(ctx, storageClassMetas) 223 | } 224 | 225 | case err := <-errCh: 226 | if err == nil { 227 | continue 228 | } 229 | return err 230 | case <-ctx.Done(): 231 | return nil 232 | } 233 | 234 | // check if tick interval config settings have changed 235 | if SDCTickInterval != config.SDCTickInterval { 236 | SDCTickInterval = config.SDCTickInterval 237 | sdcTicker = time.NewTicker(SDCTickInterval) 238 | } 239 | if VolumeTickInterval != config.VolumeTickInterval { 240 | VolumeTickInterval = config.VolumeTickInterval 241 | volumeTicker = time.NewTicker(VolumeTickInterval) 242 | } 243 | if StoragePoolTickInterval != config.StoragePoolTickInterval { 244 | StoragePoolTickInterval = config.StoragePoolTickInterval 245 | storagePoolTicker = time.NewTicker(StoragePoolTickInterval) 246 | } 247 | } 248 | } 249 | 250 | // ValidateConfig will validate the configuration and return any errors 251 | func ValidateConfig(config *Config) error { 252 | if config == nil { 253 | return fmt.Errorf("no config provided") 254 | } 255 | 256 | if config.PowerFlexClient == nil { 257 | return fmt.Errorf("no PowerFlexClient provided in config") 258 | } 259 | 260 | if config.SDCFinder == nil { 261 | return fmt.Errorf("no SDCFinder provided in config") 262 | } 263 | 264 | if config.NodeFinder == nil { 265 | return fmt.Errorf("no NodeFinder provided in config") 266 | } 267 | 268 | if config.SDCTickInterval > MaximumSDCTickInterval || config.SDCTickInterval < MinimumSDCTickInterval { 269 | return fmt.Errorf("SDC polling frequency not within allowed range of %v and %v", MinimumSDCTickInterval.String(), MaximumSDCTickInterval.String()) 270 | } 271 | 272 | if config.VolumeTickInterval > MaximumVolTickInterval || config.VolumeTickInterval < MinimumVolTickInterval { 273 | return fmt.Errorf("Volume polling frequency not within allowed range of %v and %v", MinimumVolTickInterval.String(), MaximumVolTickInterval.String()) 274 | } 275 | 276 | return nil 277 | } 278 | -------------------------------------------------------------------------------- /internal/entrypoint/testdata/test-cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF9TCCA92gAwIBAgIJAJ+hawPOaRY5MA0GCSqGSIb3DQEBCwUAMIGQMQswCQYD 3 | VQQGEwJVUzEWMBQGA1UECAwNTWFzc2FjaHVzZXR0czEPMA0GA1UEBwwGQm9zdG9u 4 | MQ0wCwYDVQQKDAREZWxsMQ0wCwYDVQQLDARJT0FEMRcwFQYDVQQDDA5vdGVsLWNv 5 | bGxlY3RvcjEhMB8GCSqGSIb3DQEJARYSYWFyb24udHllQGRlbGwuY29tMB4XDTIw 6 | MTExMzE1MDczOVoXDTMwMTExMTE1MDczOVowgZAxCzAJBgNVBAYTAlVTMRYwFAYD 7 | VQQIDA1NYXNzYWNodXNldHRzMQ8wDQYDVQQHDAZCb3N0b24xDTALBgNVBAoMBERl 8 | bGwxDTALBgNVBAsMBElPQUQxFzAVBgNVBAMMDm90ZWwtY29sbGVjdG9yMSEwHwYJ 9 | KoZIhvcNAQkBFhJhYXJvbi50eWVAZGVsbC5jb20wggIiMA0GCSqGSIb3DQEBAQUA 10 | A4ICDwAwggIKAoICAQCxS5WRu1wmfzlwz4x7dHCC4XD5rbXIfwB6coXYbINtsQj2 11 | QM6vS//9yfeRH+YA6NK1fQtRnbLcQnB6ZmZF80NlKH9wN7p2XK7vl6+O/vNzdUdj 12 | LI9/Lr31W2SRuDqDFFPSbfHZTR+0PIIfqng05/4oN82+33Q177dOEq1XMQli3cZJ 13 | 4i6+ghhDP1AOkEpRZAx0i7Aix8xnT+ciiPv6S2oC1FDXH+S0KasmCGmsqeW73Tiv 14 | 87rKfT4LpgB7/TZjIqCIHch9aYXYd3JCQqG9CdE/0WEUDonQgbEhKvePZpS9ec54 15 | 5MbOBzgtXgDLgb5uJejcUD3KufnJk8ffJwibRYqdzWIV3M7RcEYw0iuxEtBXuKQ7 16 | 7uOk0B7dXY06LBEWi3rKlKdGHKM0Ia1hSS2q0i3AP4kpyfYK4YzRIt6KqCbBVPFH 17 | 4MD7Cy4JNVjelliML9H/hVpeRtzTLYrydd+Oh6YfTloL0P5t5PnG3Y38gQSH5fM/ 18 | /IAvs5eC17CIrK6JvWuwDRyyWZ4Ep2N1loMV4dj9ZlWXzl8tS4oIdwtALfnZig7P 19 | fTAtgDHyEFFQEBdoVad1Nyg0k/ga2ia6XdJ5+gkc4eqXow82EAVoD1F76vaYcjPE 20 | JnwPL2SVjYc17g69sSmV5kAiJOfhDKgUMANr3IIQzDtc8uriGktPoU4DoB9elwID 21 | AQABo1AwTjAdBgNVHQ4EFgQUVAnYT7hYzSiPvDwyFMloSVb0Q8wwHwYDVR0jBBgw 22 | FoAUVAnYT7hYzSiPvDwyFMloSVb0Q8wwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B 23 | AQsFAAOCAgEAfgEZHgmhfb7BGe9r4wyDrieWaXa90fnOWQMh5qO3JopdO1uglF9q 24 | LOofDwbbPkaq2h6gKBEnJFvdNUfZI6S+5IsXpJiNsCBkoQOh7wtjxaitkD+E6pKi 25 | 1D2XVJWswbtQ43kQbKqJF4OCeegjoQUO6nt6ZvGJ/YxfkYshfF1uu/EMI5/7dBRy 26 | Acu1yhnqfxJ3LVFVo0yR4TiHmsaqkBTlrLWRqO8cSu68kGpGkw9od1eb00zLQBiQ 27 | tr9h79aMPOAoe4Ttx+UFJ9ubpUHPa3Ue+cc3DCMbBhmqrDjLnl2dvM2OitMblhQs 28 | 5iBDzNameqC4Go617X4Yb8f95DkT/Nzqulf907OLq1Me6erBwW8m6n0CwHBa2wXk 29 | Qwb1D0lotTqq61i0dVmGc3lD3wPEr1GkatpRdxViGLcEM847obaGEs3BxeWcx0Os 30 | 227aCf6ilhme4DHfdQaAdAGryTnsP8sUbtl5TydlqucHd8pHzlCcxdHFmz9jGOgG 31 | ZIge2J/XCdgTt+nojDBwU+nVt38xOtWBLDZ5OANiqIOCa6EOYIvXcZF38dYxPMYh 32 | nGLP1SXGTwbgbon+At2M/F0wR48u+czZPo4sGnMMcAOZEEI6SHy4zX89MsphcQOC 33 | e+tGX8yNAs5Ypc1JhZicv1DfB10ih/LkvATeWVflHedNFHAJ73R5g2U= 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /internal/k8s/k8sapi.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s 18 | 19 | import ( 20 | "context" 21 | "sync" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | v1 "k8s.io/api/storage/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | 27 | "k8s.io/client-go/kubernetes" 28 | "k8s.io/client-go/rest" 29 | ) 30 | 31 | // API holds data used to access the K8S API 32 | type API struct { 33 | Client kubernetes.Interface 34 | Lock sync.Mutex 35 | } 36 | 37 | // GetCSINodes will return a list of CSI nodes in the kubernetes cluster 38 | func (api *API) GetCSINodes() (*v1.CSINodeList, error) { 39 | api.Lock.Lock() 40 | defer api.Lock.Unlock() 41 | if api.Client == nil { 42 | err := ConnectFn(api) 43 | if err != nil { 44 | return nil, err 45 | } 46 | } 47 | return api.Client.StorageV1().CSINodes().List(context.Background(), metav1.ListOptions{}) 48 | } 49 | 50 | // GetPersistentVolumes will return a list of persistent volumes in the kubernetes cluster 51 | func (api *API) GetPersistentVolumes() (*corev1.PersistentVolumeList, error) { 52 | api.Lock.Lock() 53 | defer api.Lock.Unlock() 54 | if api.Client == nil { 55 | err := ConnectFn(api) 56 | if err != nil { 57 | return nil, err 58 | } 59 | } 60 | return api.Client.CoreV1().PersistentVolumes().List(context.Background(), metav1.ListOptions{}) 61 | } 62 | 63 | // GetStorageClasses will return a list of storage classes in the kubernetes clusteer 64 | func (api *API) GetStorageClasses() (*v1.StorageClassList, error) { 65 | api.Lock.Lock() 66 | defer api.Lock.Unlock() 67 | if api.Client == nil { 68 | err := ConnectFn(api) 69 | if err != nil { 70 | return nil, err 71 | } 72 | } 73 | return api.Client.StorageV1().StorageClasses().List(context.Background(), metav1.ListOptions{}) 74 | } 75 | 76 | // GetNodes will return the list of nodes in the kubernetes cluster 77 | func (api *API) GetNodes() (*corev1.NodeList, error) { 78 | api.Lock.Lock() 79 | defer api.Lock.Unlock() 80 | if api.Client == nil { 81 | err := ConnectFn(api) 82 | if err != nil { 83 | return nil, err 84 | } 85 | } 86 | 87 | return api.Client.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{}) 88 | } 89 | 90 | // ConnectFn will connect the client to the k8s API 91 | var ConnectFn = func(api *API) error { 92 | config, err := getConfig() 93 | if err != nil { 94 | return err 95 | } 96 | api.Client, err = NewConfigFn(config) 97 | if err != nil { 98 | return err 99 | } 100 | return nil 101 | } 102 | 103 | // InClusterConfigFn will return a valid configuration if we are running in a Pod on a kubernetes cluster 104 | var InClusterConfigFn = func() (*rest.Config, error) { 105 | return rest.InClusterConfig() 106 | } 107 | 108 | // NewConfigFn will return a valid kubernetes.Clientset 109 | var NewConfigFn = func(config *rest.Config) (*kubernetes.Clientset, error) { 110 | return kubernetes.NewForConfig(config) 111 | } 112 | 113 | func getConfig() (*rest.Config, error) { 114 | config, err := InClusterConfigFn() 115 | if err != nil { 116 | return nil, err 117 | } 118 | return config, nil 119 | } 120 | -------------------------------------------------------------------------------- /internal/k8s/k8sapi_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s_test 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "testing" 23 | 24 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s" 25 | 26 | "k8s.io/client-go/kubernetes" 27 | 28 | "github.com/stretchr/testify/assert" 29 | corev1 "k8s.io/api/core/v1" 30 | v1 "k8s.io/api/storage/v1" 31 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 | "k8s.io/client-go/kubernetes/fake" 33 | "k8s.io/client-go/rest" 34 | ) 35 | 36 | func Test_GetCSINodes(t *testing.T) { 37 | type checkFn func(*testing.T, *v1.CSINodeList, error) 38 | type connectFn func(*k8s.API) error 39 | type configFn func() (*rest.Config, error) 40 | check := func(fns ...checkFn) []checkFn { return fns } 41 | 42 | hasNoError := func(t *testing.T, _ *v1.CSINodeList, err error) { 43 | if err != nil { 44 | t.Fatalf("expected no error") 45 | } 46 | } 47 | 48 | checkExpectedOutput := func(expectedOutput *v1.CSINodeList) func(t *testing.T, nodes *v1.CSINodeList, err error) { 49 | return func(t *testing.T, nodes *v1.CSINodeList, _ error) { 50 | assert.Equal(t, expectedOutput, nodes) 51 | } 52 | } 53 | 54 | hasError := func(t *testing.T, _ *v1.CSINodeList, err error) { 55 | if err == nil { 56 | t.Fatalf("expected error") 57 | } 58 | } 59 | 60 | tests := map[string]func(t *testing.T) (connectFn, configFn, []checkFn){ 61 | "success": func(*testing.T) (connectFn, configFn, []checkFn) { 62 | nodes := &v1.CSINodeList{ 63 | Items: []v1.CSINode{ 64 | { 65 | ObjectMeta: metav1.ObjectMeta{ 66 | Name: "csi-node-1", 67 | }, 68 | Spec: v1.CSINodeSpec{ 69 | Drivers: []v1.CSINodeDriver{ 70 | { 71 | Name: "csi-vxflexos.dellemc.com", 72 | NodeID: "node-1", 73 | }, 74 | }, 75 | }, 76 | }, 77 | }, 78 | } 79 | 80 | connect := func(api *k8s.API) error { 81 | api.Client = fake.NewSimpleClientset(nodes) 82 | return nil 83 | } 84 | return connect, nil, check(hasNoError, checkExpectedOutput(nodes)) 85 | }, 86 | "error connecting": func(*testing.T) (connectFn, configFn, []checkFn) { 87 | connect := func(_ *k8s.API) error { 88 | return errors.New("error") 89 | } 90 | return connect, nil, check(hasError) 91 | }, 92 | "error getting a valid config": func(*testing.T) (connectFn, configFn, []checkFn) { 93 | inClusterConfig := func() (*rest.Config, error) { 94 | return nil, errors.New("error") 95 | } 96 | return nil, inClusterConfig, check(hasError) 97 | }, 98 | } 99 | for name, tc := range tests { 100 | t.Run(name, func(t *testing.T) { 101 | connectFn, inClusterConfig, checkFns := tc(t) 102 | k8sclient := k8s.API{} 103 | 104 | if connectFn != nil { 105 | oldConnectFn := k8s.ConnectFn 106 | defer func() { k8s.ConnectFn = oldConnectFn }() 107 | k8s.ConnectFn = connectFn 108 | } 109 | if inClusterConfig != nil { 110 | oldInClusterConfig := k8s.InClusterConfigFn 111 | defer func() { k8s.InClusterConfigFn = oldInClusterConfig }() 112 | k8s.InClusterConfigFn = inClusterConfig 113 | } 114 | nodes, err := k8sclient.GetCSINodes() 115 | for _, checkFn := range checkFns { 116 | checkFn(t, nodes, err) 117 | } 118 | }) 119 | } 120 | } 121 | 122 | func Test_GetPersistentVolumes(t *testing.T) { 123 | type checkFn func(*testing.T, *corev1.PersistentVolumeList, error) 124 | type connectFn func(*k8s.API) error 125 | type configFn func() (*rest.Config, error) 126 | check := func(fns ...checkFn) []checkFn { return fns } 127 | 128 | hasNoError := func(t *testing.T, _ *corev1.PersistentVolumeList, err error) { 129 | if err != nil { 130 | t.Fatalf("expected no error") 131 | } 132 | } 133 | 134 | checkExpectedOutput := func(expectedOutput *corev1.PersistentVolumeList) func(t *testing.T, volumes *corev1.PersistentVolumeList, err error) { 135 | return func(t *testing.T, volumes *corev1.PersistentVolumeList, _ error) { 136 | assert.Equal(t, expectedOutput, volumes) 137 | } 138 | } 139 | 140 | hasError := func(t *testing.T, _ *corev1.PersistentVolumeList, err error) { 141 | if err == nil { 142 | t.Fatalf("expected error") 143 | } 144 | } 145 | 146 | tests := map[string]func(t *testing.T) (connectFn, configFn, []checkFn){ 147 | "success": func(*testing.T) (connectFn, configFn, []checkFn) { 148 | volumes := &corev1.PersistentVolumeList{ 149 | Items: []corev1.PersistentVolume{ 150 | { 151 | ObjectMeta: metav1.ObjectMeta{ 152 | Name: "persistent-volume-name", 153 | }, 154 | }, 155 | }, 156 | } 157 | connect := func(api *k8s.API) error { 158 | api.Client = fake.NewSimpleClientset(volumes) 159 | return nil 160 | } 161 | return connect, nil, check(hasNoError, checkExpectedOutput(volumes)) 162 | }, 163 | "error connecting": func(*testing.T) (connectFn, configFn, []checkFn) { 164 | connect := func(_ *k8s.API) error { 165 | return errors.New("error") 166 | } 167 | return connect, nil, check(hasError) 168 | }, 169 | "error getting a valid config": func(*testing.T) (connectFn, configFn, []checkFn) { 170 | inClusterConfig := func() (*rest.Config, error) { 171 | return nil, errors.New("error") 172 | } 173 | return nil, inClusterConfig, check(hasError) 174 | }, 175 | } 176 | for name, tc := range tests { 177 | t.Run(name, func(t *testing.T) { 178 | connectFn, inClusterConfig, checkFns := tc(t) 179 | k8sclient := &k8s.API{} 180 | if connectFn != nil { 181 | oldConnectFn := k8s.ConnectFn 182 | defer func() { k8s.ConnectFn = oldConnectFn }() 183 | k8s.ConnectFn = connectFn 184 | } 185 | if inClusterConfig != nil { 186 | oldInClusterConfig := k8s.InClusterConfigFn 187 | defer func() { k8s.InClusterConfigFn = oldInClusterConfig }() 188 | k8s.InClusterConfigFn = inClusterConfig 189 | } 190 | volumes, err := k8sclient.GetPersistentVolumes() 191 | for _, checkFn := range checkFns { 192 | checkFn(t, volumes, err) 193 | } 194 | }) 195 | } 196 | } 197 | 198 | func Test_GetStorageClasses(t *testing.T) { 199 | type checkFn func(*testing.T, *v1.StorageClassList, error) 200 | type connectFn func(*k8s.API) error 201 | type configFn func() (*rest.Config, error) 202 | check := func(fns ...checkFn) []checkFn { return fns } 203 | 204 | hasNoError := func(t *testing.T, _ *v1.StorageClassList, err error) { 205 | if err != nil { 206 | t.Fatalf("expected no error") 207 | } 208 | } 209 | 210 | checkExpectedOutput := func(expectedOutput *v1.StorageClassList) func(t *testing.T, volumes *v1.StorageClassList, err error) { 211 | return func(t *testing.T, volumes *v1.StorageClassList, _ error) { 212 | assert.Equal(t, expectedOutput, volumes) 213 | } 214 | } 215 | 216 | hasError := func(t *testing.T, _ *v1.StorageClassList, err error) { 217 | if err == nil { 218 | t.Fatalf("expected error") 219 | } 220 | } 221 | 222 | tests := map[string]func(t *testing.T) (connectFn, configFn, []checkFn){ 223 | "success": func(*testing.T) (connectFn, configFn, []checkFn) { 224 | storageClasses := &v1.StorageClassList{ 225 | Items: []v1.StorageClass{ 226 | { 227 | ObjectMeta: metav1.ObjectMeta{ 228 | Name: "vxflexos", 229 | }, 230 | Provisioner: "csi-vxflexos.dellemc.com", 231 | Parameters: map[string]string{ 232 | "storagepool": "mypool", 233 | }, 234 | }, 235 | }, 236 | } 237 | 238 | connect := func(api *k8s.API) error { 239 | api.Client = fake.NewSimpleClientset(storageClasses) 240 | return nil 241 | } 242 | return connect, nil, check(hasNoError, checkExpectedOutput(storageClasses)) 243 | }, 244 | "error connecting": func(*testing.T) (connectFn, configFn, []checkFn) { 245 | connect := func(_ *k8s.API) error { 246 | return errors.New("error") 247 | } 248 | return connect, nil, check(hasError) 249 | }, 250 | "error getting a valid config": func(*testing.T) (connectFn, configFn, []checkFn) { 251 | inClusterConfig := func() (*rest.Config, error) { 252 | return nil, errors.New("error") 253 | } 254 | return nil, inClusterConfig, check(hasError) 255 | }, 256 | } 257 | for name, tc := range tests { 258 | t.Run(name, func(t *testing.T) { 259 | connectFn, inClusterConfig, checkFns := tc(t) 260 | k8sclient := &k8s.API{} 261 | if connectFn != nil { 262 | oldConnectFn := k8s.ConnectFn 263 | defer func() { k8s.ConnectFn = oldConnectFn }() 264 | k8s.ConnectFn = connectFn 265 | } 266 | if inClusterConfig != nil { 267 | oldInClusterConfig := k8s.InClusterConfigFn 268 | defer func() { k8s.InClusterConfigFn = oldInClusterConfig }() 269 | k8s.InClusterConfigFn = inClusterConfig 270 | } 271 | storageClasses, err := k8sclient.GetStorageClasses() 272 | for _, checkFn := range checkFns { 273 | checkFn(t, storageClasses, err) 274 | } 275 | }) 276 | } 277 | } 278 | 279 | func Test_GetNodes(t *testing.T) { 280 | type checkFn func(*testing.T, *corev1.NodeList, error) 281 | type connectFn func(*k8s.API) error 282 | type configFn func() (*rest.Config, error) 283 | check := func(fns ...checkFn) []checkFn { return fns } 284 | 285 | hasNoError := func(t *testing.T, _ *corev1.NodeList, err error) { 286 | if err != nil { 287 | t.Fatalf("expected no error") 288 | } 289 | } 290 | 291 | checkExpectedOutput := func(expectedOutput *corev1.NodeList) func(t *testing.T, nodes *corev1.NodeList, err error) { 292 | return func(t *testing.T, nodes *corev1.NodeList, _ error) { 293 | assert.Equal(t, expectedOutput, nodes) 294 | } 295 | } 296 | 297 | hasError := func(t *testing.T, _ *corev1.NodeList, err error) { 298 | if err == nil { 299 | t.Fatalf("expected error") 300 | } 301 | } 302 | 303 | tests := map[string]func(t *testing.T) (connectFn, configFn, []checkFn){ 304 | "success": func(*testing.T) (connectFn, configFn, []checkFn) { 305 | nodes := &corev1.NodeList{ 306 | Items: []corev1.Node{ 307 | { 308 | ObjectMeta: metav1.ObjectMeta{ 309 | Name: "node1", 310 | }, 311 | }, 312 | }, 313 | } 314 | 315 | connect := func(api *k8s.API) error { 316 | api.Client = fake.NewSimpleClientset(nodes) 317 | return nil 318 | } 319 | return connect, nil, check(hasNoError, checkExpectedOutput(nodes)) 320 | }, 321 | "error connecting": func(*testing.T) (connectFn, configFn, []checkFn) { 322 | connect := func(_ *k8s.API) error { 323 | return errors.New("error") 324 | } 325 | return connect, nil, check(hasError) 326 | }, 327 | "error getting a valid config": func(*testing.T) (connectFn, configFn, []checkFn) { 328 | inClusterConfig := func() (*rest.Config, error) { 329 | return nil, errors.New("error") 330 | } 331 | return nil, inClusterConfig, check(hasError) 332 | }, 333 | } 334 | for name, tc := range tests { 335 | t.Run(name, func(t *testing.T) { 336 | connectFn, inClusterConfig, checkFns := tc(t) 337 | k8sclient := &k8s.API{} 338 | if connectFn != nil { 339 | oldConnectFn := k8s.ConnectFn 340 | defer func() { k8s.ConnectFn = oldConnectFn }() 341 | k8s.ConnectFn = connectFn 342 | } 343 | if inClusterConfig != nil { 344 | oldInClusterConfig := k8s.InClusterConfigFn 345 | defer func() { k8s.InClusterConfigFn = oldInClusterConfig }() 346 | k8s.InClusterConfigFn = inClusterConfig 347 | } 348 | nodes, err := k8sclient.GetNodes() 349 | for _, checkFn := range checkFns { 350 | checkFn(t, nodes, err) 351 | } 352 | }) 353 | } 354 | } 355 | 356 | func Test_InClusterConfigFn(t *testing.T) { 357 | t.Run("success", func(t *testing.T) { 358 | _, err := k8s.InClusterConfigFn() 359 | assert.Error(t, err) 360 | }) 361 | } 362 | 363 | func Test_NewForConfigError(t *testing.T) { 364 | k8sapi := &k8s.API{} 365 | 366 | oldInClusterConfigFn := k8s.InClusterConfigFn 367 | defer func() { k8s.InClusterConfigFn = oldInClusterConfigFn }() 368 | k8s.InClusterConfigFn = func() (*rest.Config, error) { 369 | return new(rest.Config), nil 370 | } 371 | 372 | oldNewConfigFn := k8s.NewConfigFn 373 | defer func() { k8s.NewConfigFn = oldNewConfigFn }() 374 | expected := "could not create Clientset from KubeConfig" 375 | k8s.NewConfigFn = func(_ *rest.Config) (*kubernetes.Clientset, error) { 376 | return nil, fmt.Errorf("%s", expected) 377 | } 378 | 379 | _, err := k8sapi.GetStorageClasses() 380 | assert.True(t, err != nil) 381 | if err != nil { 382 | assert.Equal(t, expected, err.Error()) 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /internal/k8s/leader_elector.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "time" 23 | 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | 26 | "k8s.io/client-go/kubernetes" 27 | "k8s.io/client-go/rest" 28 | 29 | "k8s.io/client-go/tools/leaderelection" 30 | "k8s.io/client-go/tools/leaderelection/resourcelock" 31 | ) 32 | 33 | // LeaderElectorGetter is an interface for initialize and check elected leader 34 | // 35 | //go:generate mockgen -destination=mocks/leader_elector_getter_mocks.go -package=mocks github.com/amusingospre/karavi-metrics-powerflex/internal/k8s LeaderElectorGetter 36 | type LeaderElectorGetter interface { 37 | InitLeaderElection(string, string) error 38 | IsLeader() bool 39 | } 40 | 41 | // LeaderElector holds LeaderElector struct for the client 42 | type LeaderElector struct { 43 | API LeaderElectorGetter 44 | Elector *leaderelection.LeaderElector 45 | } 46 | 47 | // InitLeaderElection will run algorithm for leader election, call during service initialzation process 48 | func (elect *LeaderElector) InitLeaderElection(endpoint string, namespace string) error { 49 | k8sconfig, err := InClusterConfigFn() 50 | if err != nil { 51 | return err 52 | } 53 | 54 | k8sclient, err := NewForConfigFn(k8sconfig) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | leaderConfig := leaderelection.LeaderElectionConfig{ 60 | LeaseDuration: 15 * time.Second, 61 | RenewDeadline: 10 * time.Second, 62 | RetryPeriod: 2 * time.Second, 63 | Callbacks: leaderelection.LeaderCallbacks{ 64 | OnStartedLeading: func(context.Context) {}, 65 | OnStoppedLeading: func() {}, 66 | OnNewLeader: func(_ string) {}, 67 | }, 68 | Lock: &resourcelock.LeaseLock{ 69 | LeaseMeta: metav1.ObjectMeta{ 70 | Name: endpoint, 71 | Namespace: namespace, 72 | }, 73 | Client: k8sclient.CoordinationV1(), 74 | LockConfig: resourcelock.ResourceLockConfig{ 75 | Identity: os.Getenv("HOSTNAME"), 76 | }, 77 | }, 78 | } 79 | 80 | elect.Elector, err = NewLeaderElectorFn(leaderConfig) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | elect.Elector.Run(context.Background()) 86 | return nil 87 | } 88 | 89 | // IsLeader return true if the given client is leader at the moment 90 | func (elect *LeaderElector) IsLeader() bool { 91 | if elect.Elector == nil { 92 | return false 93 | } 94 | return elect.Elector.IsLeader() 95 | } 96 | 97 | // NewForConfigFn creates a new Clientset for the given config. If config's RateLimiter is not set and QPS and Burst are acceptable, NewForConfigFn will generate a rate-limiter in configShallowCopy 98 | var NewForConfigFn = func(k8sconfig *rest.Config) (*kubernetes.Clientset, error) { 99 | return kubernetes.NewForConfig(k8sconfig) 100 | } 101 | 102 | // NewLeaderElectorFn creates a LeaderElector from a LeaderElectionConfig 103 | var NewLeaderElectorFn = func(lec leaderelection.LeaderElectionConfig) (*leaderelection.LeaderElector, error) { 104 | return leaderelection.NewLeaderElector(lec) 105 | } 106 | -------------------------------------------------------------------------------- /internal/k8s/leader_elector_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s_test 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s" 24 | 25 | "github.com/stretchr/testify/assert" 26 | "k8s.io/client-go/kubernetes" 27 | "k8s.io/client-go/rest" 28 | "k8s.io/client-go/tools/leaderelection" 29 | ) 30 | 31 | func Test_InitLeaderElection(t *testing.T) { 32 | type checkFn func(*testing.T, error) 33 | type configFn func() (*rest.Config, error) 34 | type clientsetFn func(config *rest.Config) (*kubernetes.Clientset, error) 35 | type leaderelectionFn func(lec leaderelection.LeaderElectionConfig) (*leaderelection.LeaderElector, error) 36 | check := func(fns ...checkFn) []checkFn { return fns } 37 | 38 | hasError := func(t *testing.T, err error) { 39 | if err == nil { 40 | t.Fatalf("expected error") 41 | } 42 | } 43 | 44 | tests := map[string]func(t *testing.T) (configFn, clientsetFn, leaderelectionFn, []checkFn){ 45 | "error getting a valid config": func(*testing.T) (configFn, clientsetFn, leaderelectionFn, []checkFn) { 46 | inClusterConfig := func() (*rest.Config, error) { 47 | return nil, errors.New("error") 48 | } 49 | return inClusterConfig, nil, nil, check(hasError) 50 | }, 51 | "error getting new clientset": func(*testing.T) (configFn, clientsetFn, leaderelectionFn, []checkFn) { 52 | configFn := func() (*rest.Config, error) { 53 | return nil, nil 54 | } 55 | 56 | clientset := func(_ *rest.Config) (*kubernetes.Clientset, error) { 57 | return nil, errors.New("error") 58 | } 59 | 60 | return configFn, clientset, nil, check(hasError) 61 | }, 62 | "error create leader": func(*testing.T) (configFn, clientsetFn, leaderelectionFn, []checkFn) { 63 | configFn := func() (*rest.Config, error) { 64 | return nil, nil 65 | } 66 | 67 | clientset := func(_ *rest.Config) (*kubernetes.Clientset, error) { 68 | mockClientset := &kubernetes.Clientset{} 69 | return mockClientset, nil 70 | } 71 | leaderelection := func(_ leaderelection.LeaderElectionConfig) (*leaderelection.LeaderElector, error) { 72 | return nil, errors.New("error") 73 | } 74 | return configFn, clientset, leaderelection, check(hasError) 75 | }, 76 | } 77 | for name, tc := range tests { 78 | t.Run(name, func(t *testing.T) { 79 | configFn, clientsetFn, leaderelectionFn, checkFns := tc(t) 80 | k8sclient := k8s.LeaderElector{} 81 | if configFn != nil { 82 | oldInClusterConfig := k8s.InClusterConfigFn 83 | defer func() { k8s.InClusterConfigFn = oldInClusterConfig }() 84 | k8s.InClusterConfigFn = configFn 85 | } 86 | if clientsetFn != nil { 87 | oldNewForConfigFn := k8s.NewForConfigFn 88 | defer func() { k8s.NewForConfigFn = oldNewForConfigFn }() 89 | k8s.NewForConfigFn = clientsetFn 90 | } 91 | if leaderelectionFn != nil { 92 | oldLeaderElection := k8s.NewLeaderElectorFn 93 | defer func() { k8s.NewLeaderElectorFn = oldLeaderElection }() 94 | k8s.NewLeaderElectorFn = leaderelectionFn 95 | } 96 | err := k8sclient.InitLeaderElection("karavi-metrics-powerflex", "karavi") 97 | for _, checkFn := range checkFns { 98 | checkFn(t, err) 99 | } 100 | }) 101 | } 102 | } 103 | 104 | func Test_IsLeader(t *testing.T) { 105 | tt := []struct { 106 | Name string 107 | Input bool 108 | }{ 109 | { 110 | "Test", 111 | false, 112 | }, 113 | } 114 | 115 | for _, tc := range tt { 116 | t.Run(tc.Name, func(t *testing.T) { 117 | k8sclient := k8s.LeaderElector{} 118 | err := k8sclient.IsLeader() 119 | assert.Equal(t, err, tc.Input) 120 | }) 121 | } 122 | } 123 | 124 | func Test_NewForConfigFn(t *testing.T) { 125 | t.Run("success", func(t *testing.T) { 126 | k8sconfig := &rest.Config{} 127 | _, err := k8s.NewForConfigFn(k8sconfig) 128 | assert.Equal(t, err, nil) 129 | }) 130 | } 131 | 132 | func Test_NewLeaderElectorFn(t *testing.T) { 133 | t.Run("success", func(t *testing.T) { 134 | lec := leaderelection.LeaderElectionConfig{} 135 | _, err := k8s.NewLeaderElectorFn(lec) 136 | assert.Error(t, err) 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /internal/k8s/mocks/kubernetes_api_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/k8s (interfaces: KubernetesAPI) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | v1 "k8s.io/api/storage/v1" 12 | ) 13 | 14 | // MockKubernetesAPI is a mock of KubernetesAPI interface. 15 | type MockKubernetesAPI struct { 16 | ctrl *gomock.Controller 17 | recorder *MockKubernetesAPIMockRecorder 18 | } 19 | 20 | // MockKubernetesAPIMockRecorder is the mock recorder for MockKubernetesAPI. 21 | type MockKubernetesAPIMockRecorder struct { 22 | mock *MockKubernetesAPI 23 | } 24 | 25 | // NewMockKubernetesAPI creates a new mock instance. 26 | func NewMockKubernetesAPI(ctrl *gomock.Controller) *MockKubernetesAPI { 27 | mock := &MockKubernetesAPI{ctrl: ctrl} 28 | mock.recorder = &MockKubernetesAPIMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockKubernetesAPI) EXPECT() *MockKubernetesAPIMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetCSINodes mocks base method. 38 | func (m *MockKubernetesAPI) GetCSINodes() (*v1.CSINodeList, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetCSINodes") 41 | ret0, _ := ret[0].(*v1.CSINodeList) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetCSINodes indicates an expected call of GetCSINodes. 47 | func (mr *MockKubernetesAPIMockRecorder) GetCSINodes() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCSINodes", reflect.TypeOf((*MockKubernetesAPI)(nil).GetCSINodes)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/k8s/mocks/leader_elector_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/k8s (interfaces: LeaderElectorGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockLeaderElectorGetter is a mock of LeaderElectorGetter interface. 14 | type MockLeaderElectorGetter struct { 15 | ctrl *gomock.Controller 16 | recorder *MockLeaderElectorGetterMockRecorder 17 | } 18 | 19 | // MockLeaderElectorGetterMockRecorder is the mock recorder for MockLeaderElectorGetter. 20 | type MockLeaderElectorGetterMockRecorder struct { 21 | mock *MockLeaderElectorGetter 22 | } 23 | 24 | // NewMockLeaderElectorGetter creates a new mock instance. 25 | func NewMockLeaderElectorGetter(ctrl *gomock.Controller) *MockLeaderElectorGetter { 26 | mock := &MockLeaderElectorGetter{ctrl: ctrl} 27 | mock.recorder = &MockLeaderElectorGetterMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockLeaderElectorGetter) EXPECT() *MockLeaderElectorGetterMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // InitLeaderElection mocks base method. 37 | func (m *MockLeaderElectorGetter) InitLeaderElection(arg0, arg1 string) error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "InitLeaderElection", arg0, arg1) 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // InitLeaderElection indicates an expected call of InitLeaderElection. 45 | func (mr *MockLeaderElectorGetterMockRecorder) InitLeaderElection(arg0, arg1 interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLeaderElection", reflect.TypeOf((*MockLeaderElectorGetter)(nil).InitLeaderElection), arg0, arg1) 48 | } 49 | 50 | // IsLeader mocks base method. 51 | func (m *MockLeaderElectorGetter) IsLeader() bool { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "IsLeader") 54 | ret0, _ := ret[0].(bool) 55 | return ret0 56 | } 57 | 58 | // IsLeader indicates an expected call of IsLeader. 59 | func (mr *MockLeaderElectorGetterMockRecorder) IsLeader() *gomock.Call { 60 | mr.mock.ctrl.T.Helper() 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsLeader", reflect.TypeOf((*MockLeaderElectorGetter)(nil).IsLeader)) 62 | } 63 | -------------------------------------------------------------------------------- /internal/k8s/mocks/node_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/k8s (interfaces: NodeGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | v1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | // MockNodeGetter is a mock of NodeGetter interface. 15 | type MockNodeGetter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockNodeGetterMockRecorder 18 | } 19 | 20 | // MockNodeGetterMockRecorder is the mock recorder for MockNodeGetter. 21 | type MockNodeGetterMockRecorder struct { 22 | mock *MockNodeGetter 23 | } 24 | 25 | // NewMockNodeGetter creates a new mock instance. 26 | func NewMockNodeGetter(ctrl *gomock.Controller) *MockNodeGetter { 27 | mock := &MockNodeGetter{ctrl: ctrl} 28 | mock.recorder = &MockNodeGetterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockNodeGetter) EXPECT() *MockNodeGetterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetNodes mocks base method. 38 | func (m *MockNodeGetter) GetNodes() (*v1.NodeList, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetNodes") 41 | ret0, _ := ret[0].(*v1.NodeList) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetNodes indicates an expected call of GetNodes. 47 | func (mr *MockNodeGetterMockRecorder) GetNodes() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodes", reflect.TypeOf((*MockNodeGetter)(nil).GetNodes)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/k8s/mocks/storage_class_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/k8s (interfaces: StorageClassGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | v1 "k8s.io/api/storage/v1" 12 | ) 13 | 14 | // MockStorageClassGetter is a mock of StorageClassGetter interface. 15 | type MockStorageClassGetter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockStorageClassGetterMockRecorder 18 | } 19 | 20 | // MockStorageClassGetterMockRecorder is the mock recorder for MockStorageClassGetter. 21 | type MockStorageClassGetterMockRecorder struct { 22 | mock *MockStorageClassGetter 23 | } 24 | 25 | // NewMockStorageClassGetter creates a new mock instance. 26 | func NewMockStorageClassGetter(ctrl *gomock.Controller) *MockStorageClassGetter { 27 | mock := &MockStorageClassGetter{ctrl: ctrl} 28 | mock.recorder = &MockStorageClassGetterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockStorageClassGetter) EXPECT() *MockStorageClassGetterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetStorageClasses mocks base method. 38 | func (m *MockStorageClassGetter) GetStorageClasses() (*v1.StorageClassList, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetStorageClasses") 41 | ret0, _ := ret[0].(*v1.StorageClassList) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetStorageClasses indicates an expected call of GetStorageClasses. 47 | func (mr *MockStorageClassGetterMockRecorder) GetStorageClasses() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStorageClasses", reflect.TypeOf((*MockStorageClassGetter)(nil).GetStorageClasses)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/k8s/mocks/volume_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/k8s (interfaces: VolumeGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | v1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | // MockVolumeGetter is a mock of VolumeGetter interface. 15 | type MockVolumeGetter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockVolumeGetterMockRecorder 18 | } 19 | 20 | // MockVolumeGetterMockRecorder is the mock recorder for MockVolumeGetter. 21 | type MockVolumeGetterMockRecorder struct { 22 | mock *MockVolumeGetter 23 | } 24 | 25 | // NewMockVolumeGetter creates a new mock instance. 26 | func NewMockVolumeGetter(ctrl *gomock.Controller) *MockVolumeGetter { 27 | mock := &MockVolumeGetter{ctrl: ctrl} 28 | mock.recorder = &MockVolumeGetterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockVolumeGetter) EXPECT() *MockVolumeGetterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetPersistentVolumes mocks base method. 38 | func (m *MockVolumeGetter) GetPersistentVolumes() (*v1.PersistentVolumeList, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetPersistentVolumes") 41 | ret0, _ := ret[0].(*v1.PersistentVolumeList) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetPersistentVolumes indicates an expected call of GetPersistentVolumes. 47 | func (mr *MockVolumeGetterMockRecorder) GetPersistentVolumes() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPersistentVolumes", reflect.TypeOf((*MockVolumeGetter)(nil).GetPersistentVolumes)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/k8s/node_finder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | ) 22 | 23 | // NodeGetter is an interface for getting a list of storage class information 24 | // 25 | //go:generate mockgen -destination=mocks/node_getter_mocks.go -package=mocks github.com/amusingospre/karavi-metrics-powerflex/internal/k8s NodeGetter 26 | type NodeGetter interface { 27 | GetNodes() (*corev1.NodeList, error) 28 | } 29 | 30 | // NodeFinder is a node finder that will query the Kubernetes API for a node by its IP address 31 | type NodeFinder struct { 32 | API NodeGetter 33 | } 34 | 35 | // GetNodes will return a kubernetes Node from an IP address 36 | func (f *NodeFinder) GetNodes() ([]corev1.Node, error) { 37 | nodes, err := f.API.GetNodes() 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return nodes.Items, nil 43 | } 44 | -------------------------------------------------------------------------------- /internal/k8s/node_finder_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s_test 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s" 24 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s/mocks" 25 | 26 | "github.com/golang/mock/gomock" 27 | "github.com/stretchr/testify/assert" 28 | corev1 "k8s.io/api/core/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | ) 31 | 32 | func Test_K8sNodeFinder(t *testing.T) { 33 | type checkFn func(*testing.T, []corev1.Node, error) 34 | check := func(fns ...checkFn) []checkFn { return fns } 35 | 36 | hasNoError := func(t *testing.T, _ []corev1.Node, err error) { 37 | if err != nil { 38 | t.Fatalf("expected no error") 39 | } 40 | } 41 | 42 | checkExpectedOutput := func(expectedOutput []corev1.Node) func(t *testing.T, nodes []corev1.Node, err error) { 43 | return func(t *testing.T, nodes []corev1.Node, _ error) { 44 | assert.Equal(t, expectedOutput, nodes) 45 | } 46 | } 47 | 48 | hasError := func(t *testing.T, _ []corev1.Node, err error) { 49 | if err == nil { 50 | t.Fatalf("expected error") 51 | } 52 | } 53 | 54 | tests := map[string]func(t *testing.T) (k8s.NodeFinder, []checkFn, *gomock.Controller){ 55 | "success finding nodes": func(*testing.T) (k8s.NodeFinder, []checkFn, *gomock.Controller) { 56 | ctrl := gomock.NewController(t) 57 | api := mocks.NewMockNodeGetter(ctrl) 58 | 59 | nodes := &corev1.NodeList{ 60 | Items: []corev1.Node{ 61 | { 62 | ObjectMeta: metav1.ObjectMeta{ 63 | Name: "node1", 64 | }, 65 | Status: corev1.NodeStatus{ 66 | Addresses: []corev1.NodeAddress{ 67 | { 68 | Address: "1.2.3.4", 69 | }, 70 | }, 71 | }, 72 | }, 73 | { 74 | ObjectMeta: metav1.ObjectMeta{ 75 | Name: "node2", 76 | }, 77 | Status: corev1.NodeStatus{ 78 | Addresses: []corev1.NodeAddress{ 79 | { 80 | Address: "1.2.3.5", 81 | }, 82 | }, 83 | }, 84 | }, 85 | }, 86 | } 87 | 88 | api.EXPECT().GetNodes().Times(1).Return(nodes, nil) 89 | 90 | finder := k8s.NodeFinder{API: api} 91 | return finder, check(hasNoError, checkExpectedOutput(nodes.Items)), ctrl 92 | }, 93 | "error calling k8s": func(*testing.T) (k8s.NodeFinder, []checkFn, *gomock.Controller) { 94 | ctrl := gomock.NewController(t) 95 | api := mocks.NewMockNodeGetter(ctrl) 96 | api.EXPECT().GetNodes().Times(1).Return(nil, errors.New("error")) 97 | finder := k8s.NodeFinder{API: api} 98 | return finder, check(hasError), ctrl 99 | }, 100 | } 101 | 102 | for name, tc := range tests { 103 | t.Run(name, func(t *testing.T) { 104 | finder, checkFns, ctrl := tc(t) 105 | nodes, err := finder.GetNodes() 106 | for _, checkFn := range checkFns { 107 | checkFn(t, nodes, err) 108 | } 109 | ctrl.Finish() 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /internal/k8s/sdc_finder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s 18 | 19 | import ( 20 | "strings" 21 | 22 | v1 "k8s.io/api/storage/v1" 23 | ) 24 | 25 | // KubernetesAPI is an interface for accessing the Kubernetes API 26 | // 27 | //go:generate mockgen -destination=mocks/kubernetes_api_mocks.go -package=mocks github.com/amusingospre/karavi-metrics-powerflex/internal/k8s KubernetesAPI 28 | type KubernetesAPI interface { 29 | GetCSINodes() (*v1.CSINodeList, error) 30 | } 31 | 32 | // SDCFinder is an SDC finder that will query the Kubernetes API for CSI-Nodes that have a matching DriverName and Storage System ID 33 | type SDCFinder struct { 34 | API KubernetesAPI 35 | StorageSystemID []StorageSystemID 36 | } 37 | 38 | // GetSDCGuids will return a list of SDC GUIDs that match the given DriverName in Kubernetes 39 | func (f *SDCFinder) GetSDCGuids() ([]string, error) { 40 | var sdcGUIDS []string 41 | 42 | nodes, err := f.API.GetCSINodes() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | for _, node := range nodes.Items { 48 | for _, driver := range node.Spec.Drivers { 49 | if f.isMatch(driver) { 50 | sdcGUIDS = append(sdcGUIDS, driver.NodeID) 51 | } 52 | } 53 | } 54 | return sdcGUIDS, nil 55 | } 56 | 57 | func (f *SDCFinder) isMatch(driver v1.CSINodeDriver) bool { 58 | for _, topologyKey := range driver.TopologyKeys { 59 | split := strings.Split(topologyKey, "/") 60 | if len(split) == 2 { 61 | for _, storage := range f.StorageSystemID { 62 | if split[1] == storage.ID && Contains(storage.DriverNames, split[0]) { 63 | return true 64 | } 65 | } 66 | } 67 | } 68 | return false 69 | } 70 | 71 | // Contains will return true if the slice contains the given value 72 | func Contains(slice []string, value string) bool { 73 | for _, element := range slice { 74 | if element == value { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | -------------------------------------------------------------------------------- /internal/k8s/sdc_finder_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s_test 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s" 24 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s/mocks" 25 | 26 | v1 "k8s.io/api/storage/v1" 27 | 28 | "github.com/golang/mock/gomock" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func Test_K8sSDCFinder(t *testing.T) { 33 | type checkFn func(*testing.T, []string, error) 34 | check := func(fns ...checkFn) []checkFn { return fns } 35 | 36 | hasNoError := func(t *testing.T, _ []string, err error) { 37 | if err != nil { 38 | t.Fatalf("expected no error") 39 | } 40 | } 41 | 42 | checkExpectedOutput := func(expectedOutput []string) func(t *testing.T, sdcGuids []string, err error) { 43 | return func(t *testing.T, sdcGuids []string, _ error) { 44 | assert.Equal(t, expectedOutput, sdcGuids) 45 | } 46 | } 47 | 48 | hasError := func(t *testing.T, _ []string, err error) { 49 | if err == nil { 50 | t.Fatalf("expected error") 51 | } 52 | } 53 | 54 | tests := map[string]func(t *testing.T) (k8s.SDCFinder, []checkFn, *gomock.Controller){ 55 | "success": func(*testing.T) (k8s.SDCFinder, []checkFn, *gomock.Controller) { 56 | ctrl := gomock.NewController(t) 57 | api := mocks.NewMockKubernetesAPI(ctrl) 58 | 59 | nodes := &v1.CSINodeList{ 60 | Items: []v1.CSINode{ 61 | { 62 | Spec: v1.CSINodeSpec{ 63 | Drivers: []v1.CSINodeDriver{ 64 | { 65 | Name: "csi-vxflexos.dellemc.com", 66 | NodeID: "node-1", 67 | TopologyKeys: []string{"csi-vxflexos.dellemc.com/storage-system-id-1"}, 68 | }, 69 | }, 70 | }, 71 | }, 72 | { 73 | Spec: v1.CSINodeSpec{ 74 | Drivers: []v1.CSINodeDriver{ 75 | { 76 | Name: "other-driver-name", 77 | NodeID: "node-2", 78 | }, 79 | }, 80 | }, 81 | }, 82 | { 83 | Spec: v1.CSINodeSpec{ 84 | Drivers: []v1.CSINodeDriver{ 85 | { 86 | Name: "csi-vxflexos.dellemc.com", 87 | NodeID: "node-3", 88 | TopologyKeys: []string{"csi-vxflexos.dellemc.com/storage-system-id-1"}, 89 | }, 90 | }, 91 | }, 92 | }, 93 | { 94 | Spec: v1.CSINodeSpec{ 95 | Drivers: []v1.CSINodeDriver{ 96 | { 97 | Name: "csi-vxflexos.dellemc.com", 98 | NodeID: "node-4", 99 | TopologyKeys: []string{"csi-vxflexos.dellemc.com/storage-system-id-2"}, 100 | }, 101 | }, 102 | }, 103 | }, 104 | }, 105 | } 106 | api.EXPECT().GetCSINodes().Times(1).Return(nodes, nil) 107 | 108 | ids := make([]k8s.StorageSystemID, 1) 109 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com"}} 110 | 111 | finder := k8s.SDCFinder{API: api, StorageSystemID: ids} 112 | return finder, check(hasNoError, checkExpectedOutput([]string{"node-1", "node-3"})), ctrl 113 | }, 114 | "success with multiple driver names": func(*testing.T) (k8s.SDCFinder, []checkFn, *gomock.Controller) { 115 | ctrl := gomock.NewController(t) 116 | api := mocks.NewMockKubernetesAPI(ctrl) 117 | 118 | nodes := &v1.CSINodeList{ 119 | Items: []v1.CSINode{ 120 | { 121 | Spec: v1.CSINodeSpec{ 122 | Drivers: []v1.CSINodeDriver{ 123 | { 124 | Name: "csi-vxflexos.dellemc.com", 125 | NodeID: "node-1", 126 | TopologyKeys: []string{"csi-vxflexos.dellemc.com/storage-system-id-1"}, 127 | }, 128 | }, 129 | }, 130 | }, 131 | { 132 | Spec: v1.CSINodeSpec{ 133 | Drivers: []v1.CSINodeDriver{ 134 | { 135 | Name: "other-driver-name", 136 | NodeID: "node-2", 137 | TopologyKeys: []string{"other-driver-name/storage-system-id-1"}, 138 | }, 139 | }, 140 | }, 141 | }, 142 | { 143 | Spec: v1.CSINodeSpec{ 144 | Drivers: []v1.CSINodeDriver{ 145 | { 146 | Name: "csi-vxflexos.dellemc.com", 147 | NodeID: "node-3", 148 | TopologyKeys: []string{"csi-vxflexos.dellemc.com/storage-system-id-1"}, 149 | }, 150 | }, 151 | }, 152 | }, 153 | }, 154 | } 155 | api.EXPECT().GetCSINodes().Times(1).Return(nodes, nil) 156 | 157 | ids := make([]k8s.StorageSystemID, 1) 158 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com", "other-driver-name"}} 159 | 160 | finder := k8s.SDCFinder{API: api, StorageSystemID: ids} 161 | 162 | return finder, check(hasNoError, checkExpectedOutput([]string{"node-1", "node-2", "node-3"})), ctrl 163 | }, 164 | "error calling k8s": func(*testing.T) (k8s.SDCFinder, []checkFn, *gomock.Controller) { 165 | ctrl := gomock.NewController(t) 166 | api := mocks.NewMockKubernetesAPI(ctrl) 167 | api.EXPECT().GetCSINodes().Times(1).Return(nil, errors.New("error")) 168 | finder := k8s.SDCFinder{API: api} 169 | return finder, check(hasError), ctrl 170 | }, 171 | } 172 | for name, tc := range tests { 173 | t.Run(name, func(t *testing.T) { 174 | finder, checkFns, ctrl := tc(t) 175 | sdcGuids, err := finder.GetSDCGuids() 176 | for _, checkFn := range checkFns { 177 | checkFn(t, sdcGuids, err) 178 | } 179 | ctrl.Finish() 180 | }) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /internal/k8s/storageclass_finder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s 18 | 19 | import ( 20 | v1 "k8s.io/api/storage/v1" 21 | ) 22 | 23 | // StorageClassGetter is an interface for getting a list of storage class information 24 | // 25 | //go:generate mockgen -destination=mocks/storage_class_getter_mocks.go -package=mocks github.com/amusingospre/karavi-metrics-powerflex/internal/k8s StorageClassGetter 26 | type StorageClassGetter interface { 27 | GetStorageClasses() (*v1.StorageClassList, error) 28 | } 29 | 30 | // StorageSystemID contains ID, whether is default and associated drivernames 31 | type StorageSystemID struct { 32 | ID string 33 | IsDefault bool 34 | DriverNames []string 35 | } 36 | 37 | // StorageClassFinder is a storage class finder that will query the Kubernetes API for storage classes provisioned by a matching DriverName and StorageSystemID 38 | type StorageClassFinder struct { 39 | API StorageClassGetter 40 | StorageSystemID []StorageSystemID 41 | } 42 | 43 | // GetStorageClasses will return a list of storage classes that match the given DriverName in Kubernetes 44 | func (f *StorageClassFinder) GetStorageClasses() ([]v1.StorageClass, error) { 45 | var storageClasses []v1.StorageClass 46 | 47 | classes, err := f.API.GetStorageClasses() 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | for _, class := range classes.Items { 53 | if f.isMatch(class) { 54 | storageClasses = append(storageClasses, class) 55 | } 56 | } 57 | return storageClasses, nil 58 | } 59 | 60 | func (f *StorageClassFinder) isMatch(class v1.StorageClass) bool { 61 | for _, storage := range f.StorageSystemID { 62 | if !Contains(storage.DriverNames, class.Provisioner) { 63 | continue 64 | } 65 | 66 | systemID := class.Parameters["systemID"] 67 | 68 | if systemID == storage.ID { 69 | return true 70 | } 71 | } 72 | 73 | for _, storage := range f.StorageSystemID { 74 | if !Contains(storage.DriverNames, class.Provisioner) { 75 | continue 76 | } 77 | 78 | systemID, systemIDExists := class.Parameters["systemID"] 79 | // if a storage system is marked as default, the StorageClass is a match if either the 'systemID' key does not exist or if it matches the storage system ID 80 | if storage.IsDefault && (!systemIDExists || systemID == storage.ID) { 81 | return true 82 | } 83 | } 84 | 85 | return false 86 | } 87 | 88 | // GetStoragePools will return a list of storage pool names from a given Kubernetes storage class 89 | func GetStoragePools(storageClass v1.StorageClass) []string { 90 | return []string{storageClass.Parameters["storagepool"]} 91 | } 92 | -------------------------------------------------------------------------------- /internal/k8s/storageclass_finder_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s_test 18 | 19 | import ( 20 | "errors" 21 | "testing" 22 | 23 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s" 24 | "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s/mocks" 25 | 26 | "github.com/golang/mock/gomock" 27 | "github.com/stretchr/testify/assert" 28 | v1 "k8s.io/api/storage/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | ) 31 | 32 | func Test_K8sStorageClassFinder(t *testing.T) { 33 | type checkFn func(*testing.T, []v1.StorageClass, error) 34 | check := func(fns ...checkFn) []checkFn { return fns } 35 | 36 | hasNoError := func(t *testing.T, _ []v1.StorageClass, err error) { 37 | if err != nil { 38 | t.Fatalf("expected no error") 39 | } 40 | } 41 | 42 | checkExpectedOutput := func(expectedOutput []v1.StorageClass) func(t *testing.T, storageClasses []v1.StorageClass, err error) { 43 | return func(t *testing.T, storageClasses []v1.StorageClass, _ error) { 44 | assert.Equal(t, expectedOutput, storageClasses) 45 | } 46 | } 47 | 48 | hasError := func(t *testing.T, _ []v1.StorageClass, err error) { 49 | if err == nil { 50 | t.Fatalf("expected error") 51 | } 52 | } 53 | 54 | tests := map[string]func(t *testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller){ 55 | "success not selecting storageclass that is not in config": func(*testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller) { 56 | ctrl := gomock.NewController(t) 57 | api := mocks.NewMockStorageClassGetter(ctrl) 58 | 59 | storageClasses := &v1.StorageClassList{ 60 | Items: []v1.StorageClass{ 61 | { 62 | ObjectMeta: metav1.ObjectMeta{ 63 | Name: "vxflexos", 64 | }, 65 | Provisioner: "csi-vxflexos.dellemc.com", 66 | Parameters: map[string]string{ 67 | "storagepool": "mypool", 68 | "systemID": "notExisting", 69 | }, 70 | }, 71 | { 72 | ObjectMeta: metav1.ObjectMeta{ 73 | Name: "vxflexos-xfs", 74 | }, 75 | Provisioner: "csi-vxflexos.dellemc.com", 76 | Parameters: map[string]string{ 77 | "storagepool": "mypool", 78 | "systemID": "storage-system-id-1", 79 | }, 80 | }, 81 | }, 82 | } 83 | 84 | expected := &v1.StorageClassList{ 85 | Items: []v1.StorageClass{ 86 | storageClasses.Items[len(storageClasses.Items)-1], 87 | }, 88 | } 89 | 90 | api.EXPECT().GetStorageClasses().Times(1).Return(storageClasses, nil) 91 | ids := make([]k8s.StorageSystemID, 1) 92 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com"}, IsDefault: false} 93 | 94 | finder := k8s.StorageClassFinder{API: api, StorageSystemID: ids} 95 | return finder, check(hasNoError, checkExpectedOutput(expected.Items)), ctrl 96 | }, 97 | "success selecting the matching driver name with storage classes": func(*testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller) { 98 | ctrl := gomock.NewController(t) 99 | api := mocks.NewMockStorageClassGetter(ctrl) 100 | 101 | storageClasses := &v1.StorageClassList{ 102 | Items: []v1.StorageClass{ 103 | { 104 | ObjectMeta: metav1.ObjectMeta{ 105 | Name: "vxflexos", 106 | }, 107 | Provisioner: "csi-vxflexos.dellemc.com", 108 | Parameters: map[string]string{ 109 | "storagepool": "mypool", 110 | "systemID": "storage-system-id-1", 111 | }, 112 | }, 113 | { 114 | ObjectMeta: metav1.ObjectMeta{ 115 | Name: "vxflexos-xfs", 116 | }, 117 | Provisioner: "csi-vxflexos.dellemc.com", 118 | Parameters: map[string]string{ 119 | "storagepool": "mypool", 120 | "systemID": "storage-system-id-1", 121 | }, 122 | }, 123 | }, 124 | } 125 | 126 | api.EXPECT().GetStorageClasses().Times(1).Return(storageClasses, nil) 127 | ids := make([]k8s.StorageSystemID, 1) 128 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com"}, IsDefault: false} 129 | 130 | finder := k8s.StorageClassFinder{API: api, StorageSystemID: ids} 131 | return finder, check(hasNoError, checkExpectedOutput(storageClasses.Items)), ctrl 132 | }, 133 | "success selecting storage classes matching multiple driver names": func(*testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller) { 134 | ctrl := gomock.NewController(t) 135 | api := mocks.NewMockStorageClassGetter(ctrl) 136 | 137 | storageClasses := &v1.StorageClassList{ 138 | Items: []v1.StorageClass{ 139 | { 140 | ObjectMeta: metav1.ObjectMeta{ 141 | Name: "vxflexos", 142 | }, 143 | Provisioner: "csi-vxflexos.dellemc.com", 144 | Parameters: map[string]string{ 145 | "storagepool": "mypool", 146 | "systemID": "storage-system-id-1", 147 | }, 148 | }, 149 | { 150 | ObjectMeta: metav1.ObjectMeta{ 151 | Name: "vxflexos-xfs", 152 | }, 153 | Provisioner: "csi-vxflexos.dellemc.com", 154 | Parameters: map[string]string{ 155 | "storagepool": "mypool", 156 | "systemID": "storage-system-id-1", 157 | }, 158 | }, 159 | { 160 | ObjectMeta: metav1.ObjectMeta{ 161 | Name: "vxflexos-another", 162 | }, 163 | Provisioner: "another-csi-driver.dellemc.com", 164 | Parameters: map[string]string{ 165 | "storagepool": "mypool", 166 | "systemID": "storage-system-id-1", 167 | }, 168 | }, 169 | { 170 | ObjectMeta: metav1.ObjectMeta{ 171 | Name: "vxflexos-xfs-another", 172 | }, 173 | Provisioner: "another-csi-driver.dellemc.com", 174 | Parameters: map[string]string{ 175 | "storagepool": "mypool", 176 | "systemID": "storage-system-id-1", 177 | }, 178 | }, 179 | }, 180 | } 181 | 182 | api.EXPECT().GetStorageClasses().Times(1).Return(storageClasses, nil) 183 | ids := make([]k8s.StorageSystemID, 1) 184 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com", "another-csi-driver.dellemc.com"}, IsDefault: false} 185 | 186 | finder := k8s.StorageClassFinder{API: api, StorageSystemID: ids} 187 | 188 | return finder, check(hasNoError, checkExpectedOutput(storageClasses.Items)), ctrl 189 | }, 190 | "success matching storage classes without systemID based on a default system being used": func(*testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller) { 191 | ctrl := gomock.NewController(t) 192 | api := mocks.NewMockStorageClassGetter(ctrl) 193 | 194 | storageClasses := &v1.StorageClassList{ 195 | Items: []v1.StorageClass{ 196 | { 197 | ObjectMeta: metav1.ObjectMeta{ 198 | Name: "vxflexos", 199 | }, 200 | Provisioner: "csi-vxflexos.dellemc.com", 201 | Parameters: map[string]string{ 202 | "storagepool": "mypool", 203 | }, 204 | }, 205 | { 206 | ObjectMeta: metav1.ObjectMeta{ 207 | Name: "vxflexos-xfs", 208 | }, 209 | Provisioner: "csi-vxflexos.dellemc.com", 210 | Parameters: map[string]string{ 211 | "storagepool": "mypool", 212 | "systemID": "storage-system-id-2", 213 | }, 214 | }, 215 | { 216 | ObjectMeta: metav1.ObjectMeta{ 217 | Name: "another-pool", 218 | }, 219 | Provisioner: "csi-vxflexos.dellemc.com", 220 | Parameters: map[string]string{ 221 | "storagepool": "mypool", 222 | "systemID": "storage-system-id-1", 223 | }, 224 | }, 225 | }, 226 | } 227 | 228 | api.EXPECT().GetStorageClasses().Times(1).Return(storageClasses, nil) 229 | 230 | ids := make([]k8s.StorageSystemID, 1) 231 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com", "another-csi-driver.dellemc.com"}, IsDefault: true} 232 | 233 | finder := k8s.StorageClassFinder{API: api, StorageSystemID: ids} 234 | 235 | return finder, check(hasNoError, checkExpectedOutput([]v1.StorageClass{ 236 | { 237 | ObjectMeta: metav1.ObjectMeta{ 238 | Name: "vxflexos", 239 | }, 240 | Provisioner: "csi-vxflexos.dellemc.com", 241 | Parameters: map[string]string{ 242 | "storagepool": "mypool", 243 | }, 244 | }, 245 | { 246 | ObjectMeta: metav1.ObjectMeta{ 247 | Name: "another-pool", 248 | }, 249 | Provisioner: "csi-vxflexos.dellemc.com", 250 | Parameters: map[string]string{ 251 | "storagepool": "mypool", 252 | "systemID": "storage-system-id-1", 253 | }, 254 | }, 255 | }, 256 | )), ctrl 257 | }, 258 | "success selecting storage classes matching one of two driver names": func(*testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller) { 259 | ctrl := gomock.NewController(t) 260 | api := mocks.NewMockStorageClassGetter(ctrl) 261 | 262 | storageClasses := &v1.StorageClassList{ 263 | Items: []v1.StorageClass{ 264 | { 265 | ObjectMeta: metav1.ObjectMeta{ 266 | Name: "vxflexos", 267 | }, 268 | Provisioner: "csi-vxflexos.dellemc.com", 269 | Parameters: map[string]string{ 270 | "storagepool": "mypool", 271 | "systemID": "storage-system-id-1", 272 | }, 273 | }, 274 | { 275 | ObjectMeta: metav1.ObjectMeta{ 276 | Name: "vxflexos-xfs", 277 | }, 278 | Provisioner: "csi-vxflexos.dellemc.com", 279 | Parameters: map[string]string{ 280 | "storagepool": "mypool", 281 | "systemID": "storage-system-id-1", 282 | }, 283 | }, 284 | { 285 | ObjectMeta: metav1.ObjectMeta{ 286 | Name: "vxflexos-another", 287 | }, 288 | Provisioner: "another-vxflexos.dellemc.com", 289 | Parameters: map[string]string{ 290 | "storagepool": "mypool", 291 | "systemID": "storage-system-id-1", 292 | }, 293 | }, 294 | { 295 | ObjectMeta: metav1.ObjectMeta{ 296 | Name: "vxflexos-xfs-another", 297 | }, 298 | Provisioner: "another-vxflexos.dellemc.com", 299 | Parameters: map[string]string{ 300 | "storagepool": "mypool", 301 | "systemID": "storage-system-id-1", 302 | }, 303 | }, 304 | }, 305 | } 306 | 307 | api.EXPECT().GetStorageClasses().Times(1).Return(storageClasses, nil) 308 | 309 | ids := make([]k8s.StorageSystemID, 1) 310 | ids[0] = k8s.StorageSystemID{ID: "storage-system-id-1", DriverNames: []string{"csi-vxflexos.dellemc.com"}} 311 | 312 | finder := k8s.StorageClassFinder{API: api, StorageSystemID: ids} 313 | 314 | return finder, check(hasNoError, checkExpectedOutput([]v1.StorageClass{ 315 | { 316 | ObjectMeta: metav1.ObjectMeta{ 317 | Name: "vxflexos", 318 | }, 319 | Provisioner: "csi-vxflexos.dellemc.com", 320 | Parameters: map[string]string{ 321 | "storagepool": "mypool", 322 | "systemID": "storage-system-id-1", 323 | }, 324 | }, 325 | { 326 | ObjectMeta: metav1.ObjectMeta{ 327 | Name: "vxflexos-xfs", 328 | }, 329 | Provisioner: "csi-vxflexos.dellemc.com", 330 | Parameters: map[string]string{ 331 | "storagepool": "mypool", 332 | "systemID": "storage-system-id-1", 333 | }, 334 | }, 335 | }, 336 | )), ctrl 337 | }, 338 | "error calling k8s": func(*testing.T) (k8s.StorageClassFinder, []checkFn, *gomock.Controller) { 339 | ctrl := gomock.NewController(t) 340 | api := mocks.NewMockStorageClassGetter(ctrl) 341 | api.EXPECT().GetStorageClasses().Times(1).Return(nil, errors.New("error")) 342 | finder := k8s.StorageClassFinder{API: api} 343 | return finder, check(hasError), ctrl 344 | }, 345 | } 346 | for name, tc := range tests { 347 | t.Run(name, func(t *testing.T) { 348 | finder, checkFns, ctrl := tc(t) 349 | storageClasses, err := finder.GetStorageClasses() 350 | for _, checkFn := range checkFns { 351 | checkFn(t, storageClasses, err) 352 | } 353 | ctrl.Finish() 354 | }) 355 | } 356 | } 357 | 358 | func Test_GetStoragePools(t *testing.T) { 359 | t.Run("success, single storage pool retrieved from storage class", func(t *testing.T) { 360 | sc := v1.StorageClass{ 361 | ObjectMeta: metav1.ObjectMeta{ 362 | Name: "vxflexos", 363 | }, 364 | Provisioner: "csi-vxflexos.dellemc.com", 365 | Parameters: map[string]string{ 366 | "storagepool": "mypool", 367 | "systemID": "storage-system-id-1", 368 | }, 369 | } 370 | 371 | storagePools := k8s.GetStoragePools(sc) 372 | expectedOutput := []string{"mypool"} 373 | assert.Equal(t, expectedOutput, storagePools) 374 | }) 375 | } 376 | -------------------------------------------------------------------------------- /internal/k8s/testdata/.kube/config: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://1.2.3.4:6443 5 | name: kubernetes 6 | contexts: 7 | - context: 8 | cluster: kubernetes 9 | user: kubernetes-admin 10 | name: kubernetes-admin@kubernetes 11 | current-context: kubernetes-admin@kubernetes 12 | kind: Config 13 | preferences: {} 14 | users: 15 | - name: kubernetes-admin 16 | -------------------------------------------------------------------------------- /internal/k8s/volume_finder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8s 18 | 19 | import ( 20 | "errors" 21 | "strings" 22 | 23 | "github.com/sirupsen/logrus" 24 | corev1 "k8s.io/api/core/v1" 25 | v1 "k8s.io/api/core/v1" 26 | ) 27 | 28 | // VolumeGetter is an interface for getting a list of persistent volume information 29 | // 30 | //go:generate mockgen -destination=mocks/volume_getter_mocks.go -package=mocks github.com/amusingospre/karavi-metrics-powerflex/internal/k8s VolumeGetter 31 | type VolumeGetter interface { 32 | GetPersistentVolumes() (*corev1.PersistentVolumeList, error) 33 | } 34 | 35 | // VolumeFinder is a volume finder that will query the Kubernetes API for Persistent Volumes created by a matching DriverName and StorageSystemID 36 | type VolumeFinder struct { 37 | API VolumeGetter 38 | StorageSystemID []StorageSystemID 39 | Logger *logrus.Logger 40 | } 41 | 42 | // VolumeInfo contains information about mapping a Persistent Volume to the volume created on a storage system 43 | type VolumeInfo struct { 44 | Namespace string `json:"namespace"` 45 | PersistentVolumeClaim string `json:"persistent_volume_claim"` 46 | PersistentVolumeStatus string `json:"volume_status"` 47 | VolumeClaimName string `json:"volume_claim_name"` 48 | PersistentVolume string `json:"persistent_volume"` 49 | StorageClass string `json:"storage_class"` 50 | Driver string `json:"driver"` 51 | ProvisionedSize string `json:"provisioned_size"` 52 | StorageSystemVolumeName string `json:"storage_system_volume_name"` 53 | StoragePoolName string `json:"storage_pool_name"` 54 | StorageSystemID string `json:"storage_system_id"` 55 | CreatedTime string `json:"created_time"` 56 | } 57 | 58 | // GetPersistentVolumes will return a list of persistent volume information 59 | func (f VolumeFinder) GetPersistentVolumes() ([]VolumeInfo, error) { 60 | volumeInfo := make([]VolumeInfo, 0) 61 | 62 | volumes, err := f.API.GetPersistentVolumes() 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | for _, volume := range volumes.Items { 68 | if f.isMatch(volume) { 69 | capacity := volume.Spec.Capacity[v1.ResourceStorage] 70 | claim := volume.Spec.ClaimRef 71 | status := volume.Status 72 | storageystemid, err := f.getStorageID(volume) 73 | if err != nil { 74 | f.Logger.WithField("volume name", volume.Name).Warn("no storage system id found") 75 | continue 76 | } 77 | 78 | // Check added to skip PV s which do not have any PVC s 79 | if volume.Spec.ClaimRef == nil { 80 | f.Logger.Debugf("The PV, %s , do not have a claim \n", volume.Name) 81 | continue 82 | } 83 | 84 | info := VolumeInfo{ 85 | Namespace: claim.Namespace, 86 | PersistentVolumeClaim: string(claim.UID), 87 | VolumeClaimName: claim.Name, 88 | PersistentVolumeStatus: string(status.Phase), 89 | PersistentVolume: volume.Name, 90 | StorageClass: volume.Spec.StorageClassName, 91 | Driver: volume.Spec.CSI.Driver, 92 | ProvisionedSize: capacity.String(), 93 | StorageSystemVolumeName: volume.Spec.CSI.VolumeAttributes["Name"], 94 | StoragePoolName: volume.Spec.CSI.VolumeAttributes["StoragePoolName"], 95 | StorageSystemID: storageystemid, 96 | CreatedTime: volume.CreationTimestamp.String(), 97 | } 98 | volumeInfo = append(volumeInfo, info) 99 | } 100 | } 101 | return volumeInfo, nil 102 | } 103 | 104 | func (f *VolumeFinder) isMatch(volume v1.PersistentVolume) bool { 105 | if volume.Spec.CSI == nil { 106 | return false 107 | } 108 | // volumeHandle is storageSystemID-volumeID 109 | volstorageid, err := f.getStorageID(volume) 110 | if err != nil { 111 | f.Logger.WithField("volume name", volume.Name).Warn("no storage system id found") 112 | return false 113 | } 114 | for _, storageSystemID := range f.StorageSystemID { 115 | if volstorageid == storageSystemID.ID && Contains(storageSystemID.DriverNames, volume.Spec.CSI.Driver) { 116 | return true 117 | } 118 | } 119 | return false 120 | } 121 | 122 | func (f *VolumeFinder) getStorageID(volume v1.PersistentVolume) (string, error) { 123 | if volume.Spec.CSI == nil { 124 | return "", errors.New("storage system id not found") 125 | } 126 | // volumeHandle is storageSystemID-volumeID 127 | split := strings.Split(volume.Spec.CSI.VolumeHandle, "-") 128 | if len(split) == 2 { 129 | return split[0], nil 130 | } 131 | return "", errors.New("storage system id not found") 132 | } 133 | -------------------------------------------------------------------------------- /internal/service/configuration_reader.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2023 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package service 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | 24 | "sigs.k8s.io/yaml" 25 | ) 26 | 27 | // ArrayConnectionData contains data required to connect to array 28 | type ArrayConnectionData struct { 29 | SystemID string `json:"systemID"` 30 | Username string `json:"username"` 31 | Password string `json:"password"` 32 | Endpoint string `json:"endpoint"` 33 | Insecure bool `json:"insecure,omitempty"` 34 | IsDefault bool `json:"isDefault,omitempty"` 35 | SkipCertificateValidation bool `json:"skipCertificateValidation,omitempty"` 36 | } 37 | 38 | // ConfigurationReader handles reading of the storage system configuration secret 39 | type ConfigurationReader struct{} 40 | 41 | // GetStorageSystemConfiguration returns a storage system from the configuration file 42 | // If no default system is supplied, the first system in the list is returned 43 | func (c *ConfigurationReader) GetStorageSystemConfiguration(file string) ([]ArrayConnectionData, error) { 44 | if _, err := os.Stat(file); os.IsNotExist(err) { 45 | return nil, fmt.Errorf("%s", fmt.Sprintf("File %s does not exist", file)) 46 | } 47 | 48 | config, err := os.ReadFile(filepath.Clean(file)) 49 | if err != nil { 50 | return nil, fmt.Errorf("%s", fmt.Sprintf("File %s errors: %v", file, err)) 51 | } 52 | 53 | if string(config) == "" { 54 | return nil, fmt.Errorf("arrays details are not provided in vxflexos-config secret") 55 | } 56 | 57 | connectionData := make([]ArrayConnectionData, 0) 58 | // support backward compatibility 59 | config, err = yaml.JSONToYAML(config) 60 | if err != nil { 61 | return nil, fmt.Errorf("%s", fmt.Sprintf("converting json to yaml: %v", err)) 62 | } 63 | 64 | err = yaml.Unmarshal(config, &connectionData) 65 | if err != nil { 66 | return nil, fmt.Errorf("%s", fmt.Sprintf("Unable to parse the credentials: %v", err)) 67 | } 68 | 69 | if len(connectionData) == 0 { 70 | return nil, fmt.Errorf("no arrays are provided in vxflexos-config secret") 71 | } 72 | 73 | for i, c := range connectionData { 74 | err := validateStorageSystem(c, i) 75 | if err != nil { 76 | return nil, err 77 | } 78 | } 79 | 80 | return connectionData, nil 81 | } 82 | 83 | func validateStorageSystem(system ArrayConnectionData, i int) error { 84 | if system.SystemID == "" { 85 | return fmt.Errorf("%s", fmt.Sprintf("invalid value for system name at index %d", i)) 86 | } 87 | if system.Username == "" { 88 | return fmt.Errorf("%s", fmt.Sprintf("invalid value for Username at index %d", i)) 89 | } 90 | if system.Password == "" { 91 | return fmt.Errorf("%s", fmt.Sprintf("invalid value for Password at index %d", i)) 92 | } 93 | if system.Endpoint == "" { 94 | return fmt.Errorf("%s", fmt.Sprintf("invalid value for Endpoint at index %d", i)) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /internal/service/configuration_reader_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package service_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/amusingospre/karavi-metrics-powerflex/internal/service" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func Test_ConfigurationReader(t *testing.T) { 27 | type checkFn func(*testing.T, []service.ArrayConnectionData, error) 28 | check := func(fns ...checkFn) []checkFn { return fns } 29 | 30 | hasNoError := func(t *testing.T, _ []service.ArrayConnectionData, err error) { 31 | if err != nil { 32 | t.Fatalf("expected no error") 33 | } 34 | } 35 | 36 | checkExpectedOutput := func(expectedOutput []service.ArrayConnectionData) func(t *testing.T, result []service.ArrayConnectionData, err error) { 37 | return func(t *testing.T, result []service.ArrayConnectionData, _ error) { 38 | assert.Equal(t, expectedOutput, result) 39 | } 40 | } 41 | 42 | hasError := func(t *testing.T, _ []service.ArrayConnectionData, err error) { 43 | if err == nil { 44 | t.Fatalf("expected error") 45 | } 46 | } 47 | 48 | tests := map[string]func(t *testing.T) (service.ConfigurationReader, string, []checkFn){ 49 | "success json with no default system in config": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 50 | file := "testdata/config-with-no-default.json" 51 | configReader := service.ConfigurationReader{} 52 | 53 | expectedResult := []service.ArrayConnectionData{ 54 | { 55 | Username: "admin", 56 | Password: "password", 57 | SystemID: "ID1", 58 | Endpoint: "http://127.0.0.1", 59 | Insecure: true, 60 | IsDefault: false, 61 | }, 62 | { 63 | Username: "admin", 64 | Password: "password", 65 | SystemID: "ID2", 66 | Endpoint: "https://127.0.0.2", 67 | Insecure: true, 68 | }, 69 | } 70 | 71 | return configReader, file, check(hasNoError, checkExpectedOutput(expectedResult)) 72 | }, 73 | "success yaml with no default system in config": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 74 | file := "testdata/config-with-no-default.yaml" 75 | configReader := service.ConfigurationReader{} 76 | 77 | expectedResult := []service.ArrayConnectionData{ 78 | { 79 | Username: "admin", 80 | Password: "password", 81 | SystemID: "ID1", 82 | Endpoint: "http://127.0.0.1", 83 | Insecure: true, 84 | IsDefault: false, 85 | }, 86 | { 87 | Username: "admin", 88 | Password: "password", 89 | SystemID: "ID2", 90 | Endpoint: "https://127.0.0.2", 91 | Insecure: true, 92 | }, 93 | } 94 | 95 | return configReader, file, check(hasNoError, checkExpectedOutput(expectedResult)) 96 | }, 97 | "success yaml with skipCertificateValidation": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 98 | file := "testdata/config-with-skipCertificateValidation.yaml" 99 | configReader := service.ConfigurationReader{} 100 | 101 | expectedResult := []service.ArrayConnectionData{ 102 | { 103 | Username: "admin", 104 | Password: "password", 105 | SystemID: "ID1", 106 | Endpoint: "http://127.0.0.1", 107 | Insecure: false, 108 | SkipCertificateValidation: true, 109 | IsDefault: false, 110 | }, 111 | { 112 | Username: "admin", 113 | Password: "password", 114 | SystemID: "ID2", 115 | Endpoint: "https://127.0.0.2", 116 | Insecure: false, 117 | SkipCertificateValidation: false, 118 | }, 119 | } 120 | 121 | return configReader, file, check(hasNoError, checkExpectedOutput(expectedResult)) 122 | }, 123 | "error when file doesn't exist": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 124 | file := "testdata/non-existant-file.json" 125 | configReader := service.ConfigurationReader{} 126 | return configReader, file, check(hasError) 127 | }, 128 | "error when file has 0 storage sysytems": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 129 | file := "testdata/config-with-0-storage-systems.json" 130 | configReader := service.ConfigurationReader{} 131 | return configReader, file, check(hasError) 132 | }, 133 | "error when file is empty": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 134 | file := "testdata/config-empty-file.json" 135 | configReader := service.ConfigurationReader{} 136 | return configReader, file, check(hasError) 137 | }, 138 | "error when file has invalid format": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 139 | file := "testdata/config-invalid-format.json" 140 | configReader := service.ConfigurationReader{} 141 | return configReader, file, check(hasError) 142 | }, 143 | "error when file has missing endpoint": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 144 | file := "testdata/config-missing-endpoint.json" 145 | configReader := service.ConfigurationReader{} 146 | return configReader, file, check(hasError) 147 | }, 148 | "error when file has missing password": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 149 | file := "testdata/config-missing-password.json" 150 | configReader := service.ConfigurationReader{} 151 | return configReader, file, check(hasError) 152 | }, 153 | "error when file has missing systemid": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 154 | file := "testdata/config-missing-systemid.json" 155 | configReader := service.ConfigurationReader{} 156 | return configReader, file, check(hasError) 157 | }, 158 | "error when file has missing username": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 159 | file := "testdata/config-missing-username.json" 160 | configReader := service.ConfigurationReader{} 161 | return configReader, file, check(hasError) 162 | }, 163 | "error when file has invalid default system": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 164 | file := "testdata/config-with-invalid-default-system.json" 165 | configReader := service.ConfigurationReader{} 166 | return configReader, file, check(hasError) 167 | }, 168 | "error when using directory as config file": func(*testing.T) (service.ConfigurationReader, string, []checkFn) { 169 | file := "testdata/" 170 | configReader := service.ConfigurationReader{} 171 | return configReader, file, check(hasError) 172 | }, 173 | } 174 | 175 | for name, tc := range tests { 176 | t.Run(name, func(t *testing.T) { 177 | configReader, file, checkFns := tc(t) 178 | storageSystemConfiguration, err := configReader.GetStorageSystemConfiguration(file) 179 | for _, checkFn := range checkFns { 180 | checkFn(t, storageSystemConfiguration, err) 181 | } 182 | }) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /internal/service/metrics.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package service 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "sync" 23 | 24 | "go.opentelemetry.io/otel/attribute" 25 | "go.opentelemetry.io/otel/metric" 26 | ) 27 | 28 | // MetricsRecorder supports recording I/O metrics 29 | // 30 | //go:generate mockgen -destination=mocks/metrics_mocks.go -package=mocks github.com/amusingospre/karavi-metrics-powerflex/internal/service MetricsRecorder,MeterCreater 31 | type MetricsRecorder interface { 32 | Record(ctx context.Context, meta interface{}, 33 | readBW, writeBW, 34 | readIOPS, writeIOPS, 35 | readLatency, writeLatency float64) error 36 | RecordCapacity(ctx context.Context, meta interface{}, 37 | totalLogicalCapacity, logicalCapacityAvailable, logicalCapacityInUse, logicalProvisioned float64) error 38 | } 39 | 40 | // MeterCreater interface is used to create and provide Meter instances, which are used to report measurements. 41 | // 42 | //go:generate mockgen -destination=mocks/meter_mocks.go -package=mocks go.opentelemetry.io/otel/metric Meter 43 | type MeterCreater interface { 44 | // AsyncFloat64() asyncfloat64.InstrumentProvider 45 | MeterProvider() metric.Meter 46 | // metric.Float64ObservableUpDownCounter 47 | } 48 | 49 | // MetricsWrapper contains data used for pushing metrics data 50 | type MetricsWrapper struct { 51 | Meter metric.Meter 52 | Metrics sync.Map 53 | Labels sync.Map 54 | CapacityMetrics sync.Map 55 | } 56 | 57 | // Metrics contains the list of metrics data that is collected 58 | type Metrics struct { 59 | ReadBW metric.Float64ObservableUpDownCounter 60 | WriteBW metric.Float64ObservableUpDownCounter 61 | ReadIOPS metric.Float64ObservableUpDownCounter 62 | WriteIOPS metric.Float64ObservableUpDownCounter 63 | ReadLatency metric.Float64ObservableUpDownCounter 64 | WriteLatency metric.Float64ObservableUpDownCounter 65 | } 66 | 67 | // CapacityMetrics contains the metrics related to a capacity 68 | type CapacityMetrics struct { 69 | TotalLogicalCapacity metric.Float64ObservableUpDownCounter 70 | LogicalCapacityAvailable metric.Float64ObservableUpDownCounter 71 | LogicalCapacityInUse metric.Float64ObservableUpDownCounter 72 | LogicalProvisioned metric.Float64ObservableUpDownCounter 73 | } 74 | 75 | func (mw *MetricsWrapper) initMetrics(prefix, metaID string, labels []attribute.KeyValue) (*Metrics, error) { 76 | readBW, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "read_bw_megabytes_per_second") 77 | 78 | writeBW, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "write_bw_megabytes_per_second") 79 | 80 | readIOPS, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "read_iops_per_second") 81 | 82 | writeIOPS, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "write_iops_per_second") 83 | 84 | readLatency, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "read_latency_milliseconds") 85 | 86 | writeLatency, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "write_latency_milliseconds") 87 | 88 | metrics := &Metrics{ 89 | ReadBW: readBW, 90 | WriteBW: writeBW, 91 | ReadIOPS: readIOPS, 92 | WriteIOPS: writeIOPS, 93 | ReadLatency: readLatency, 94 | WriteLatency: writeLatency, 95 | } 96 | 97 | mw.Metrics.Store(metaID, metrics) 98 | mw.Labels.Store(metaID, labels) 99 | 100 | return metrics, nil 101 | } 102 | 103 | func (mw *MetricsWrapper) initCapacityMetrics(prefix, metaID string, _ []attribute.KeyValue) (*CapacityMetrics, error) { 104 | totalLogicalCapacity, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "total_logical_capacity_gigabytes") 105 | 106 | logicalCapacityAvailable, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "logical_capacity_available_gigabytes") 107 | 108 | logicalCapacityInUse, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "logical_capacity_in_use_gigabytes") 109 | 110 | logicalProvisioned, _ := mw.Meter.Float64ObservableUpDownCounter(prefix + "logical_provisioned_gigabytes") 111 | 112 | metrics := &CapacityMetrics{ 113 | TotalLogicalCapacity: totalLogicalCapacity, 114 | LogicalCapacityAvailable: logicalCapacityAvailable, 115 | LogicalCapacityInUse: logicalCapacityInUse, 116 | LogicalProvisioned: logicalProvisioned, 117 | } 118 | 119 | mw.CapacityMetrics.Store(metaID, metrics) 120 | 121 | return metrics, nil 122 | } 123 | 124 | // Record will publish metrics data for a given instance 125 | func (mw *MetricsWrapper) Record(_ context.Context, meta interface{}, 126 | readBW, writeBW, 127 | readIOPS, writeIOPS, 128 | readLatency, writeLatency float64, 129 | ) error { 130 | var prefix string 131 | var metaID string 132 | var labels []attribute.KeyValue 133 | switch v := meta.(type) { 134 | case *VolumeMeta: 135 | prefix, metaID = "powerflex_volume_", v.ID 136 | mappedSDCIDs := "__" 137 | mappedSDCIPs := "__" 138 | for _, ip := range v.MappedSDCs { 139 | mappedSDCIDs += (ip.SdcID + "__") 140 | mappedSDCIPs += (ip.SdcIP + "__") 141 | } 142 | labels = []attribute.KeyValue{ 143 | attribute.String("VolumeID", v.ID), 144 | attribute.String("VolumeName", v.Name), 145 | attribute.String("StorageSystemID", v.StorageSystemID), 146 | attribute.String("PersistentVolumeName", v.PersistentVolumeName), 147 | attribute.String("PersistentVolumeClaimName", v.PersistentVolumeClaimName), 148 | attribute.String("Namespace", v.Namespace), 149 | attribute.String("MappedNodeIDs", mappedSDCIDs), 150 | attribute.String("MappedNodeIPs", mappedSDCIPs), 151 | attribute.String("PlotWithMean", "No"), 152 | } 153 | case *SDCMeta: 154 | prefix, metaID = "powerflex_export_node_", v.ID 155 | labels = []attribute.KeyValue{ 156 | attribute.String("ID", v.ID), 157 | attribute.String("Name", v.Name), 158 | attribute.String("IP", v.IP), 159 | attribute.String("NodeGUID", v.SdcGUID), 160 | attribute.String("PlotWithMean", "No"), 161 | } 162 | default: 163 | return errors.New("unknown MetaData type") 164 | } 165 | 166 | metricsMapValue, ok := mw.Metrics.Load(metaID) 167 | if !ok { 168 | newMetrics, err := mw.initMetrics(prefix, metaID, labels) 169 | if err != nil { 170 | return err 171 | } 172 | metricsMapValue = newMetrics 173 | } else { 174 | // If Metrics for this MetricsWrapper exist, then update the labels 175 | currentLabels, ok := mw.Labels.Load(metaID) 176 | if ok { 177 | currentLabels := currentLabels.([]attribute.KeyValue) 178 | updatedLabels := currentLabels 179 | haveLabelsChanged := false 180 | for i, current := range currentLabels { 181 | for _, new := range labels { 182 | if current.Key == new.Key { 183 | if current.Value != new.Value { 184 | updatedLabels[i].Value = new.Value 185 | haveLabelsChanged = true 186 | } 187 | } 188 | } 189 | } 190 | if haveLabelsChanged { 191 | newMetrics, err := mw.initMetrics(prefix, metaID, updatedLabels) 192 | if err != nil { 193 | return err 194 | } 195 | metricsMapValue = newMetrics 196 | } 197 | } 198 | } 199 | 200 | metrics := metricsMapValue.(*Metrics) 201 | 202 | //_, _ = mw.Meter.RegisterCallback(func(_ context.Context, obs metric.Observer) error { 203 | done := make(chan struct{}) 204 | 205 | reg, err := mw.Meter.RegisterCallback(func(_ context.Context, obs metric.Observer) error { 206 | obs.ObserveFloat64(metrics.ReadBW, float64(readBW), metric.ObserveOption(metric.WithAttributes(labels...))) 207 | obs.ObserveFloat64(metrics.WriteBW, float64(writeBW), metric.ObserveOption(metric.WithAttributes(labels...))) 208 | obs.ObserveFloat64(metrics.ReadIOPS, float64(readIOPS), metric.ObserveOption(metric.WithAttributes(labels...))) 209 | obs.ObserveFloat64(metrics.WriteIOPS, float64(writeIOPS), metric.ObserveOption(metric.WithAttributes(labels...))) 210 | obs.ObserveFloat64(metrics.ReadLatency, float64(readLatency), metric.ObserveOption(metric.WithAttributes(labels...))) 211 | obs.ObserveFloat64(metrics.WriteLatency, float64(writeLatency), metric.ObserveOption(metric.WithAttributes(labels...))) 212 | go func() { 213 | done <- struct{}{} 214 | }() 215 | return nil 216 | }, 217 | metrics.ReadBW, 218 | metrics.WriteBW, 219 | metrics.ReadIOPS, 220 | metrics.WriteIOPS, 221 | metrics.ReadLatency, 222 | metrics.WriteLatency, 223 | ) 224 | if err != nil { 225 | return err 226 | } 227 | <-done 228 | _ = reg.Unregister() 229 | 230 | return nil 231 | } 232 | 233 | // RecordCapacity will publish capacity metrics for a given instance 234 | func (mw *MetricsWrapper) RecordCapacity(_ context.Context, meta interface{}, 235 | totalLogicalCapacity, logicalCapacityAvailable, logicalCapacityInUse, logicalProvisioned float64, 236 | ) error { 237 | switch v := meta.(type) { 238 | case StorageClassMeta: 239 | switch v.Driver { 240 | case "csi-vxflexos.dellemc.com": 241 | prefix, metaID := "powerflex_storage_pool_", v.ID 242 | for pool := range v.StoragePools { 243 | labels := []attribute.KeyValue{ 244 | attribute.String("StorageClass", v.Name), 245 | attribute.String("Driver", v.Driver), 246 | attribute.String("StoragePool", pool), 247 | attribute.String("StorageSystemID", v.StorageSystemID), 248 | } 249 | 250 | metricsMapValue, ok := mw.CapacityMetrics.Load(metaID) 251 | if !ok { 252 | newMetrics, err := mw.initCapacityMetrics(prefix, metaID, labels) 253 | if err != nil { 254 | return err 255 | } 256 | metricsMapValue = newMetrics 257 | } 258 | 259 | metrics := metricsMapValue.(*CapacityMetrics) 260 | done := make(chan struct{}) 261 | reg, err := mw.Meter.RegisterCallback(func(_ context.Context, obs metric.Observer) error { 262 | obs.ObserveFloat64(metrics.TotalLogicalCapacity, float64(totalLogicalCapacity), metric.ObserveOption(metric.WithAttributes(labels...))) 263 | obs.ObserveFloat64(metrics.LogicalCapacityAvailable, float64(logicalCapacityAvailable), metric.ObserveOption(metric.WithAttributes(labels...))) 264 | obs.ObserveFloat64(metrics.LogicalCapacityInUse, float64(logicalCapacityInUse), metric.ObserveOption(metric.WithAttributes(labels...))) 265 | obs.ObserveFloat64(metrics.LogicalProvisioned, float64(logicalProvisioned), metric.ObserveOption(metric.WithAttributes(labels...))) 266 | go func() { 267 | done <- struct{}{} 268 | }() 269 | return nil 270 | }, 271 | metrics.TotalLogicalCapacity, 272 | metrics.LogicalCapacityAvailable, 273 | metrics.LogicalCapacityInUse, 274 | metrics.LogicalProvisioned, 275 | ) 276 | if err != nil { 277 | return err 278 | } 279 | <-done 280 | _ = reg.Unregister() 281 | } 282 | } 283 | default: 284 | return errors.New("unknown MetaData type") 285 | } 286 | return nil 287 | } 288 | -------------------------------------------------------------------------------- /internal/service/metrics_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package service_test 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | types "github.com/dell/goscaleio/types/v1" 24 | "github.com/amusingospre/karavi-metrics-powerflex/internal/service" 25 | otlexporters "github.com/amusingospre/karavi-metrics-powerflex/opentelemetry/exporters" 26 | "go.opentelemetry.io/otel" 27 | ) 28 | 29 | func TestMetricsWrapper_Record(t *testing.T) { 30 | mw := &service.MetricsWrapper{ 31 | Meter: otel.Meter("powerflex-test"), 32 | } 33 | volumeMetas := []interface{}{ 34 | &service.VolumeMeta{ 35 | ID: "123", 36 | }, 37 | &service.SDCMeta{ 38 | ID: "123", 39 | }, 40 | } 41 | storageClassMetas := []interface{}{ 42 | &service.StorageClassMeta{ 43 | ID: "123", 44 | }, 45 | } 46 | 47 | exporter := &otlexporters.OtlCollectorExporter{} 48 | err := exporter.InitExporter() 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | type args struct { 54 | ctx context.Context 55 | meta interface{} 56 | readBW float64 57 | writeBW float64 58 | readIOPS float64 59 | writeIOPS float64 60 | readLatency float64 61 | writeLatency float64 62 | } 63 | tests := []struct { 64 | name string 65 | mw *service.MetricsWrapper 66 | args args 67 | wantErr bool 68 | }{ 69 | { 70 | name: "success", 71 | mw: mw, 72 | args: args{ 73 | ctx: context.Background(), 74 | meta: volumeMetas[0], 75 | readBW: 1, 76 | writeBW: 2, 77 | readIOPS: 3, 78 | writeIOPS: 4, 79 | readLatency: 5, 80 | writeLatency: 6, 81 | }, 82 | wantErr: false, 83 | }, 84 | { 85 | name: "fail", 86 | mw: mw, 87 | args: args{ 88 | ctx: context.Background(), 89 | meta: storageClassMetas[0], 90 | readBW: 1, 91 | writeBW: 2, 92 | readIOPS: 3, 93 | writeIOPS: 4, 94 | readLatency: 5, 95 | writeLatency: 6, 96 | }, 97 | wantErr: true, 98 | }, 99 | } 100 | for _, tt := range tests { 101 | t.Run(tt.name, func(t *testing.T) { 102 | if err := tt.mw.Record(tt.args.ctx, tt.args.meta, tt.args.readBW, tt.args.writeBW, tt.args.readIOPS, tt.args.writeIOPS, tt.args.readLatency, tt.args.writeLatency); (err != nil) != tt.wantErr { 103 | t.Errorf("MetricsWrapper.Record() error = %v, wantErr %v", err, tt.wantErr) 104 | } 105 | }) 106 | } 107 | } 108 | 109 | type MockStoragePoolStatisticsGetter struct{} 110 | 111 | func (m *MockStoragePoolStatisticsGetter) GetStatistics() (*types.Statistics, error) { 112 | return &types.Statistics{}, nil 113 | } 114 | 115 | func TestMetricsWrapper_RecordCapacity(t *testing.T) { 116 | mw := &service.MetricsWrapper{ 117 | Meter: otel.Meter("powerflex-test"), 118 | } 119 | 120 | storageClassMeta := service.StorageClassMeta{ 121 | ID: "test-id", 122 | Name: "test-name", 123 | Driver: "csi-vxflexos.dellemc.com", 124 | StorageSystemID: "test-system-id", 125 | StoragePools: map[string]service.StoragePoolStatisticsGetter{ 126 | "pool1": &MockStoragePoolStatisticsGetter{}, 127 | }, 128 | } 129 | volumeMeta := &service.VolumeMeta{ 130 | Name: "newVolume", 131 | } 132 | totalLogicalCapacity := 100.0 133 | logicalCapacityAvailable := 50.0 134 | logicalCapacityInUse := 30.0 135 | logicalProvisioned := 20.0 136 | type args struct { 137 | ctx context.Context 138 | meta interface{} 139 | totalLogicalCapacity float64 140 | logicalCapacityAvailable float64 141 | logicalCapacityInUse float64 142 | logicalProvisioned float64 143 | } 144 | 145 | exporter := &otlexporters.OtlCollectorExporter{} 146 | err := exporter.InitExporter() 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | 151 | tests := []struct { 152 | name string 153 | mw *service.MetricsWrapper 154 | args args 155 | wantErr bool 156 | }{ 157 | { 158 | name: "success", 159 | mw: mw, 160 | args: args{ 161 | ctx: context.Background(), 162 | meta: storageClassMeta, 163 | totalLogicalCapacity: totalLogicalCapacity, 164 | logicalCapacityAvailable: logicalCapacityAvailable, 165 | logicalCapacityInUse: logicalCapacityInUse, 166 | logicalProvisioned: logicalProvisioned, 167 | }, 168 | wantErr: false, 169 | }, 170 | { 171 | name: "fail", 172 | mw: mw, 173 | args: args{ 174 | ctx: context.Background(), 175 | meta: volumeMeta, 176 | totalLogicalCapacity: totalLogicalCapacity, 177 | logicalCapacityAvailable: logicalCapacityAvailable, 178 | logicalCapacityInUse: logicalCapacityInUse, 179 | logicalProvisioned: logicalProvisioned, 180 | }, 181 | wantErr: true, 182 | }, 183 | } 184 | for _, tt := range tests { 185 | t.Run(tt.name, func(t *testing.T) { 186 | if err := tt.mw.RecordCapacity(tt.args.ctx, tt.args.meta, tt.args.totalLogicalCapacity, tt.args.logicalCapacityAvailable, tt.args.logicalCapacityInUse, tt.args.logicalProvisioned); (err != nil) != tt.wantErr { 187 | t.Errorf("MetricsWrapper.RecordCapacity() error = %v, wantErr %v", err, tt.wantErr) 188 | } 189 | }) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /internal/service/mocks/leader_elector_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: LeaderElector) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockLeaderElector is a mock of LeaderElector interface. 14 | type MockLeaderElector struct { 15 | ctrl *gomock.Controller 16 | recorder *MockLeaderElectorMockRecorder 17 | } 18 | 19 | // MockLeaderElectorMockRecorder is the mock recorder for MockLeaderElector. 20 | type MockLeaderElectorMockRecorder struct { 21 | mock *MockLeaderElector 22 | } 23 | 24 | // NewMockLeaderElector creates a new mock instance. 25 | func NewMockLeaderElector(ctrl *gomock.Controller) *MockLeaderElector { 26 | mock := &MockLeaderElector{ctrl: ctrl} 27 | mock.recorder = &MockLeaderElectorMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockLeaderElector) EXPECT() *MockLeaderElectorMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // InitLeaderElection mocks base method. 37 | func (m *MockLeaderElector) InitLeaderElection(arg0, arg1 string) error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "InitLeaderElection", arg0, arg1) 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // InitLeaderElection indicates an expected call of InitLeaderElection. 45 | func (mr *MockLeaderElectorMockRecorder) InitLeaderElection(arg0, arg1 interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLeaderElection", reflect.TypeOf((*MockLeaderElector)(nil).InitLeaderElection), arg0, arg1) 48 | } 49 | 50 | // IsLeader mocks base method. 51 | func (m *MockLeaderElector) IsLeader() bool { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "IsLeader") 54 | ret0, _ := ret[0].(bool) 55 | return ret0 56 | } 57 | 58 | // IsLeader indicates an expected call of IsLeader. 59 | func (mr *MockLeaderElectorMockRecorder) IsLeader() *gomock.Call { 60 | mr.mock.ctrl.T.Helper() 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsLeader", reflect.TypeOf((*MockLeaderElector)(nil).IsLeader)) 62 | } 63 | -------------------------------------------------------------------------------- /internal/service/mocks/meter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: go.opentelemetry.io/otel/metric (interfaces: Meter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | metric "go.opentelemetry.io/otel/metric" 12 | ) 13 | 14 | // MockMeter is a mock of Meter interface. 15 | type MockMeter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockMeterMockRecorder 18 | } 19 | 20 | // MockMeterMockRecorder is the mock recorder for MockMeter. 21 | type MockMeterMockRecorder struct { 22 | mock *MockMeter 23 | } 24 | 25 | // NewMockMeter creates a new mock instance. 26 | func NewMockMeter(ctrl *gomock.Controller) *MockMeter { 27 | mock := &MockMeter{ctrl: ctrl} 28 | mock.recorder = &MockMeterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockMeter) EXPECT() *MockMeterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // Float64Counter mocks base method. 38 | func (m *MockMeter) Float64Counter(arg0 string, arg1 ...metric.Float64CounterOption) (metric.Float64Counter, error) { 39 | m.ctrl.T.Helper() 40 | varargs := []interface{}{arg0} 41 | for _, a := range arg1 { 42 | varargs = append(varargs, a) 43 | } 44 | ret := m.ctrl.Call(m, "Float64Counter", varargs...) 45 | ret0, _ := ret[0].(metric.Float64Counter) 46 | ret1, _ := ret[1].(error) 47 | return ret0, ret1 48 | } 49 | 50 | // Float64Counter indicates an expected call of Float64Counter. 51 | func (mr *MockMeterMockRecorder) Float64Counter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 52 | mr.mock.ctrl.T.Helper() 53 | varargs := append([]interface{}{arg0}, arg1...) 54 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64Counter", reflect.TypeOf((*MockMeter)(nil).Float64Counter), varargs...) 55 | } 56 | 57 | // Float64Gauge mocks base method. 58 | func (m *MockMeter) Float64Gauge(arg0 string, arg1 ...metric.Float64GaugeOption) (metric.Float64Gauge, error) { 59 | m.ctrl.T.Helper() 60 | varargs := []interface{}{arg0} 61 | for _, a := range arg1 { 62 | varargs = append(varargs, a) 63 | } 64 | ret := m.ctrl.Call(m, "Float64Gauge", varargs...) 65 | ret0, _ := ret[0].(metric.Float64Gauge) 66 | ret1, _ := ret[1].(error) 67 | return ret0, ret1 68 | } 69 | 70 | // Float64Gauge indicates an expected call of Float64Gauge. 71 | func (mr *MockMeterMockRecorder) Float64Gauge(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 72 | mr.mock.ctrl.T.Helper() 73 | varargs := append([]interface{}{arg0}, arg1...) 74 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64Gauge", reflect.TypeOf((*MockMeter)(nil).Float64Gauge), varargs...) 75 | } 76 | 77 | // Float64Histogram mocks base method. 78 | func (m *MockMeter) Float64Histogram(arg0 string, arg1 ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { 79 | m.ctrl.T.Helper() 80 | varargs := []interface{}{arg0} 81 | for _, a := range arg1 { 82 | varargs = append(varargs, a) 83 | } 84 | ret := m.ctrl.Call(m, "Float64Histogram", varargs...) 85 | ret0, _ := ret[0].(metric.Float64Histogram) 86 | ret1, _ := ret[1].(error) 87 | return ret0, ret1 88 | } 89 | 90 | // Float64Histogram indicates an expected call of Float64Histogram. 91 | func (mr *MockMeterMockRecorder) Float64Histogram(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 92 | mr.mock.ctrl.T.Helper() 93 | varargs := append([]interface{}{arg0}, arg1...) 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64Histogram", reflect.TypeOf((*MockMeter)(nil).Float64Histogram), varargs...) 95 | } 96 | 97 | // Float64ObservableCounter mocks base method. 98 | func (m *MockMeter) Float64ObservableCounter(arg0 string, arg1 ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { 99 | m.ctrl.T.Helper() 100 | varargs := []interface{}{arg0} 101 | for _, a := range arg1 { 102 | varargs = append(varargs, a) 103 | } 104 | ret := m.ctrl.Call(m, "Float64ObservableCounter", varargs...) 105 | ret0, _ := ret[0].(metric.Float64ObservableCounter) 106 | ret1, _ := ret[1].(error) 107 | return ret0, ret1 108 | } 109 | 110 | // Float64ObservableCounter indicates an expected call of Float64ObservableCounter. 111 | func (mr *MockMeterMockRecorder) Float64ObservableCounter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 112 | mr.mock.ctrl.T.Helper() 113 | varargs := append([]interface{}{arg0}, arg1...) 114 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64ObservableCounter", reflect.TypeOf((*MockMeter)(nil).Float64ObservableCounter), varargs...) 115 | } 116 | 117 | // Float64ObservableGauge mocks base method. 118 | func (m *MockMeter) Float64ObservableGauge(arg0 string, arg1 ...metric.Float64ObservableGaugeOption) (metric.Float64ObservableGauge, error) { 119 | m.ctrl.T.Helper() 120 | varargs := []interface{}{arg0} 121 | for _, a := range arg1 { 122 | varargs = append(varargs, a) 123 | } 124 | ret := m.ctrl.Call(m, "Float64ObservableGauge", varargs...) 125 | ret0, _ := ret[0].(metric.Float64ObservableGauge) 126 | ret1, _ := ret[1].(error) 127 | return ret0, ret1 128 | } 129 | 130 | // Float64ObservableGauge indicates an expected call of Float64ObservableGauge. 131 | func (mr *MockMeterMockRecorder) Float64ObservableGauge(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 132 | mr.mock.ctrl.T.Helper() 133 | varargs := append([]interface{}{arg0}, arg1...) 134 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64ObservableGauge", reflect.TypeOf((*MockMeter)(nil).Float64ObservableGauge), varargs...) 135 | } 136 | 137 | // Float64ObservableUpDownCounter mocks base method. 138 | func (m *MockMeter) Float64ObservableUpDownCounter(arg0 string, arg1 ...metric.Float64ObservableUpDownCounterOption) (metric.Float64ObservableUpDownCounter, error) { 139 | m.ctrl.T.Helper() 140 | varargs := []interface{}{arg0} 141 | for _, a := range arg1 { 142 | varargs = append(varargs, a) 143 | } 144 | ret := m.ctrl.Call(m, "Float64ObservableUpDownCounter", varargs...) 145 | ret0, _ := ret[0].(metric.Float64ObservableUpDownCounter) 146 | ret1, _ := ret[1].(error) 147 | return ret0, ret1 148 | } 149 | 150 | // Float64ObservableUpDownCounter indicates an expected call of Float64ObservableUpDownCounter. 151 | func (mr *MockMeterMockRecorder) Float64ObservableUpDownCounter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 152 | mr.mock.ctrl.T.Helper() 153 | varargs := append([]interface{}{arg0}, arg1...) 154 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64ObservableUpDownCounter", reflect.TypeOf((*MockMeter)(nil).Float64ObservableUpDownCounter), varargs...) 155 | } 156 | 157 | // Float64UpDownCounter mocks base method. 158 | func (m *MockMeter) Float64UpDownCounter(arg0 string, arg1 ...metric.Float64UpDownCounterOption) (metric.Float64UpDownCounter, error) { 159 | m.ctrl.T.Helper() 160 | varargs := []interface{}{arg0} 161 | for _, a := range arg1 { 162 | varargs = append(varargs, a) 163 | } 164 | ret := m.ctrl.Call(m, "Float64UpDownCounter", varargs...) 165 | ret0, _ := ret[0].(metric.Float64UpDownCounter) 166 | ret1, _ := ret[1].(error) 167 | return ret0, ret1 168 | } 169 | 170 | // Float64UpDownCounter indicates an expected call of Float64UpDownCounter. 171 | func (mr *MockMeterMockRecorder) Float64UpDownCounter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 172 | mr.mock.ctrl.T.Helper() 173 | varargs := append([]interface{}{arg0}, arg1...) 174 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Float64UpDownCounter", reflect.TypeOf((*MockMeter)(nil).Float64UpDownCounter), varargs...) 175 | } 176 | 177 | // Int64Counter mocks base method. 178 | func (m *MockMeter) Int64Counter(arg0 string, arg1 ...metric.Int64CounterOption) (metric.Int64Counter, error) { 179 | m.ctrl.T.Helper() 180 | varargs := []interface{}{arg0} 181 | for _, a := range arg1 { 182 | varargs = append(varargs, a) 183 | } 184 | ret := m.ctrl.Call(m, "Int64Counter", varargs...) 185 | ret0, _ := ret[0].(metric.Int64Counter) 186 | ret1, _ := ret[1].(error) 187 | return ret0, ret1 188 | } 189 | 190 | // Int64Counter indicates an expected call of Int64Counter. 191 | func (mr *MockMeterMockRecorder) Int64Counter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 192 | mr.mock.ctrl.T.Helper() 193 | varargs := append([]interface{}{arg0}, arg1...) 194 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64Counter", reflect.TypeOf((*MockMeter)(nil).Int64Counter), varargs...) 195 | } 196 | 197 | // Int64Gauge mocks base method. 198 | func (m *MockMeter) Int64Gauge(arg0 string, arg1 ...metric.Int64GaugeOption) (metric.Int64Gauge, error) { 199 | m.ctrl.T.Helper() 200 | varargs := []interface{}{arg0} 201 | for _, a := range arg1 { 202 | varargs = append(varargs, a) 203 | } 204 | ret := m.ctrl.Call(m, "Int64Gauge", varargs...) 205 | ret0, _ := ret[0].(metric.Int64Gauge) 206 | ret1, _ := ret[1].(error) 207 | return ret0, ret1 208 | } 209 | 210 | // Int64Gauge indicates an expected call of Int64Gauge. 211 | func (mr *MockMeterMockRecorder) Int64Gauge(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 212 | mr.mock.ctrl.T.Helper() 213 | varargs := append([]interface{}{arg0}, arg1...) 214 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64Gauge", reflect.TypeOf((*MockMeter)(nil).Int64Gauge), varargs...) 215 | } 216 | 217 | // Int64Histogram mocks base method. 218 | func (m *MockMeter) Int64Histogram(arg0 string, arg1 ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { 219 | m.ctrl.T.Helper() 220 | varargs := []interface{}{arg0} 221 | for _, a := range arg1 { 222 | varargs = append(varargs, a) 223 | } 224 | ret := m.ctrl.Call(m, "Int64Histogram", varargs...) 225 | ret0, _ := ret[0].(metric.Int64Histogram) 226 | ret1, _ := ret[1].(error) 227 | return ret0, ret1 228 | } 229 | 230 | // Int64Histogram indicates an expected call of Int64Histogram. 231 | func (mr *MockMeterMockRecorder) Int64Histogram(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 232 | mr.mock.ctrl.T.Helper() 233 | varargs := append([]interface{}{arg0}, arg1...) 234 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64Histogram", reflect.TypeOf((*MockMeter)(nil).Int64Histogram), varargs...) 235 | } 236 | 237 | // Int64ObservableCounter mocks base method. 238 | func (m *MockMeter) Int64ObservableCounter(arg0 string, arg1 ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { 239 | m.ctrl.T.Helper() 240 | varargs := []interface{}{arg0} 241 | for _, a := range arg1 { 242 | varargs = append(varargs, a) 243 | } 244 | ret := m.ctrl.Call(m, "Int64ObservableCounter", varargs...) 245 | ret0, _ := ret[0].(metric.Int64ObservableCounter) 246 | ret1, _ := ret[1].(error) 247 | return ret0, ret1 248 | } 249 | 250 | // Int64ObservableCounter indicates an expected call of Int64ObservableCounter. 251 | func (mr *MockMeterMockRecorder) Int64ObservableCounter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 252 | mr.mock.ctrl.T.Helper() 253 | varargs := append([]interface{}{arg0}, arg1...) 254 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64ObservableCounter", reflect.TypeOf((*MockMeter)(nil).Int64ObservableCounter), varargs...) 255 | } 256 | 257 | // Int64ObservableGauge mocks base method. 258 | func (m *MockMeter) Int64ObservableGauge(arg0 string, arg1 ...metric.Int64ObservableGaugeOption) (metric.Int64ObservableGauge, error) { 259 | m.ctrl.T.Helper() 260 | varargs := []interface{}{arg0} 261 | for _, a := range arg1 { 262 | varargs = append(varargs, a) 263 | } 264 | ret := m.ctrl.Call(m, "Int64ObservableGauge", varargs...) 265 | ret0, _ := ret[0].(metric.Int64ObservableGauge) 266 | ret1, _ := ret[1].(error) 267 | return ret0, ret1 268 | } 269 | 270 | // Int64ObservableGauge indicates an expected call of Int64ObservableGauge. 271 | func (mr *MockMeterMockRecorder) Int64ObservableGauge(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 272 | mr.mock.ctrl.T.Helper() 273 | varargs := append([]interface{}{arg0}, arg1...) 274 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64ObservableGauge", reflect.TypeOf((*MockMeter)(nil).Int64ObservableGauge), varargs...) 275 | } 276 | 277 | // Int64ObservableUpDownCounter mocks base method. 278 | func (m *MockMeter) Int64ObservableUpDownCounter(arg0 string, arg1 ...metric.Int64ObservableUpDownCounterOption) (metric.Int64ObservableUpDownCounter, error) { 279 | m.ctrl.T.Helper() 280 | varargs := []interface{}{arg0} 281 | for _, a := range arg1 { 282 | varargs = append(varargs, a) 283 | } 284 | ret := m.ctrl.Call(m, "Int64ObservableUpDownCounter", varargs...) 285 | ret0, _ := ret[0].(metric.Int64ObservableUpDownCounter) 286 | ret1, _ := ret[1].(error) 287 | return ret0, ret1 288 | } 289 | 290 | // Int64ObservableUpDownCounter indicates an expected call of Int64ObservableUpDownCounter. 291 | func (mr *MockMeterMockRecorder) Int64ObservableUpDownCounter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 292 | mr.mock.ctrl.T.Helper() 293 | varargs := append([]interface{}{arg0}, arg1...) 294 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64ObservableUpDownCounter", reflect.TypeOf((*MockMeter)(nil).Int64ObservableUpDownCounter), varargs...) 295 | } 296 | 297 | // Int64UpDownCounter mocks base method. 298 | func (m *MockMeter) Int64UpDownCounter(arg0 string, arg1 ...metric.Int64UpDownCounterOption) (metric.Int64UpDownCounter, error) { 299 | m.ctrl.T.Helper() 300 | varargs := []interface{}{arg0} 301 | for _, a := range arg1 { 302 | varargs = append(varargs, a) 303 | } 304 | ret := m.ctrl.Call(m, "Int64UpDownCounter", varargs...) 305 | ret0, _ := ret[0].(metric.Int64UpDownCounter) 306 | ret1, _ := ret[1].(error) 307 | return ret0, ret1 308 | } 309 | 310 | // Int64UpDownCounter indicates an expected call of Int64UpDownCounter. 311 | func (mr *MockMeterMockRecorder) Int64UpDownCounter(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 312 | mr.mock.ctrl.T.Helper() 313 | varargs := append([]interface{}{arg0}, arg1...) 314 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Int64UpDownCounter", reflect.TypeOf((*MockMeter)(nil).Int64UpDownCounter), varargs...) 315 | } 316 | 317 | // RegisterCallback mocks base method. 318 | func (m *MockMeter) RegisterCallback(arg0 metric.Callback, arg1 ...metric.Observable) (metric.Registration, error) { 319 | m.ctrl.T.Helper() 320 | varargs := []interface{}{arg0} 321 | for _, a := range arg1 { 322 | varargs = append(varargs, a) 323 | } 324 | ret := m.ctrl.Call(m, "RegisterCallback", varargs...) 325 | ret0, _ := ret[0].(metric.Registration) 326 | ret1, _ := ret[1].(error) 327 | return ret0, ret1 328 | } 329 | 330 | // RegisterCallback indicates an expected call of RegisterCallback. 331 | func (mr *MockMeterMockRecorder) RegisterCallback(arg0 interface{}, arg1 ...interface{}) *gomock.Call { 332 | mr.mock.ctrl.T.Helper() 333 | varargs := append([]interface{}{arg0}, arg1...) 334 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterCallback", reflect.TypeOf((*MockMeter)(nil).RegisterCallback), varargs...) 335 | } 336 | 337 | // meter mocks base method. 338 | func (m *MockMeter) meter() { 339 | m.ctrl.T.Helper() 340 | m.ctrl.Call(m, "meter") 341 | } 342 | 343 | // meter indicates an expected call of meter. 344 | func (mr *MockMeterMockRecorder) meter() *gomock.Call { 345 | mr.mock.ctrl.T.Helper() 346 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "meter", reflect.TypeOf((*MockMeter)(nil).meter)) 347 | } 348 | -------------------------------------------------------------------------------- /internal/service/mocks/metrics_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: MetricsRecorder,MeterCreater) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | metric "go.opentelemetry.io/otel/metric" 13 | ) 14 | 15 | // MockMetricsRecorder is a mock of MetricsRecorder interface. 16 | type MockMetricsRecorder struct { 17 | ctrl *gomock.Controller 18 | recorder *MockMetricsRecorderMockRecorder 19 | } 20 | 21 | // MockMetricsRecorderMockRecorder is the mock recorder for MockMetricsRecorder. 22 | type MockMetricsRecorderMockRecorder struct { 23 | mock *MockMetricsRecorder 24 | } 25 | 26 | // NewMockMetricsRecorder creates a new mock instance. 27 | func NewMockMetricsRecorder(ctrl *gomock.Controller) *MockMetricsRecorder { 28 | mock := &MockMetricsRecorder{ctrl: ctrl} 29 | mock.recorder = &MockMetricsRecorderMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockMetricsRecorder) EXPECT() *MockMetricsRecorderMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Record mocks base method. 39 | func (m *MockMetricsRecorder) Record(arg0 context.Context, arg1 interface{}, arg2, arg3, arg4, arg5, arg6, arg7 float64) error { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "Record", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) 42 | ret0, _ := ret[0].(error) 43 | return ret0 44 | } 45 | 46 | // Record indicates an expected call of Record. 47 | func (mr *MockMetricsRecorderMockRecorder) Record(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Record", reflect.TypeOf((*MockMetricsRecorder)(nil).Record), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) 50 | } 51 | 52 | // RecordCapacity mocks base method. 53 | func (m *MockMetricsRecorder) RecordCapacity(arg0 context.Context, arg1 interface{}, arg2, arg3, arg4, arg5 float64) error { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "RecordCapacity", arg0, arg1, arg2, arg3, arg4, arg5) 56 | ret0, _ := ret[0].(error) 57 | return ret0 58 | } 59 | 60 | // RecordCapacity indicates an expected call of RecordCapacity. 61 | func (mr *MockMetricsRecorderMockRecorder) RecordCapacity(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { 62 | mr.mock.ctrl.T.Helper() 63 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordCapacity", reflect.TypeOf((*MockMetricsRecorder)(nil).RecordCapacity), arg0, arg1, arg2, arg3, arg4, arg5) 64 | } 65 | 66 | // MockMeterCreater is a mock of MeterCreater interface. 67 | type MockMeterCreater struct { 68 | ctrl *gomock.Controller 69 | recorder *MockMeterCreaterMockRecorder 70 | } 71 | 72 | // MockMeterCreaterMockRecorder is the mock recorder for MockMeterCreater. 73 | type MockMeterCreaterMockRecorder struct { 74 | mock *MockMeterCreater 75 | } 76 | 77 | // NewMockMeterCreater creates a new mock instance. 78 | func NewMockMeterCreater(ctrl *gomock.Controller) *MockMeterCreater { 79 | mock := &MockMeterCreater{ctrl: ctrl} 80 | mock.recorder = &MockMeterCreaterMockRecorder{mock} 81 | return mock 82 | } 83 | 84 | // EXPECT returns an object that allows the caller to indicate expected use. 85 | func (m *MockMeterCreater) EXPECT() *MockMeterCreaterMockRecorder { 86 | return m.recorder 87 | } 88 | 89 | // MeterProvider mocks base method. 90 | func (m *MockMeterCreater) MeterProvider() metric.Meter { 91 | m.ctrl.T.Helper() 92 | ret := m.ctrl.Call(m, "MeterProvider") 93 | ret0, _ := ret[0].(metric.Meter) 94 | return ret0 95 | } 96 | 97 | // MeterProvider indicates an expected call of MeterProvider. 98 | func (mr *MockMeterCreaterMockRecorder) MeterProvider() *gomock.Call { 99 | mr.mock.ctrl.T.Helper() 100 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MeterProvider", reflect.TypeOf((*MockMeterCreater)(nil).MeterProvider)) 101 | } 102 | -------------------------------------------------------------------------------- /internal/service/mocks/node_finder_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: NodeFinder) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | v1 "k8s.io/api/core/v1" 12 | ) 13 | 14 | // MockNodeFinder is a mock of NodeFinder interface. 15 | type MockNodeFinder struct { 16 | ctrl *gomock.Controller 17 | recorder *MockNodeFinderMockRecorder 18 | } 19 | 20 | // MockNodeFinderMockRecorder is the mock recorder for MockNodeFinder. 21 | type MockNodeFinderMockRecorder struct { 22 | mock *MockNodeFinder 23 | } 24 | 25 | // NewMockNodeFinder creates a new mock instance. 26 | func NewMockNodeFinder(ctrl *gomock.Controller) *MockNodeFinder { 27 | mock := &MockNodeFinder{ctrl: ctrl} 28 | mock.recorder = &MockNodeFinderMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockNodeFinder) EXPECT() *MockNodeFinderMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetNodes mocks base method. 38 | func (m *MockNodeFinder) GetNodes() ([]v1.Node, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetNodes") 41 | ret0, _ := ret[0].([]v1.Node) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetNodes indicates an expected call of GetNodes. 47 | func (mr *MockNodeFinderMockRecorder) GetNodes() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodes", reflect.TypeOf((*MockNodeFinder)(nil).GetNodes)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/service/mocks/powerflex_client_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: PowerFlexClient) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | goscaleio "github.com/dell/goscaleio" 11 | goscaleio0 "github.com/dell/goscaleio/types/v1" 12 | gomock "github.com/golang/mock/gomock" 13 | ) 14 | 15 | // MockPowerFlexClient is a mock of PowerFlexClient interface. 16 | type MockPowerFlexClient struct { 17 | ctrl *gomock.Controller 18 | recorder *MockPowerFlexClientMockRecorder 19 | } 20 | 21 | // MockPowerFlexClientMockRecorder is the mock recorder for MockPowerFlexClient. 22 | type MockPowerFlexClientMockRecorder struct { 23 | mock *MockPowerFlexClient 24 | } 25 | 26 | // NewMockPowerFlexClient creates a new mock instance. 27 | func NewMockPowerFlexClient(ctrl *gomock.Controller) *MockPowerFlexClient { 28 | mock := &MockPowerFlexClient{ctrl: ctrl} 29 | mock.recorder = &MockPowerFlexClientMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockPowerFlexClient) EXPECT() *MockPowerFlexClientMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Authenticate mocks base method. 39 | func (m *MockPowerFlexClient) Authenticate(arg0 *goscaleio.ConfigConnect) (goscaleio.Cluster, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "Authenticate", arg0) 42 | ret0, _ := ret[0].(goscaleio.Cluster) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // Authenticate indicates an expected call of Authenticate. 48 | func (mr *MockPowerFlexClientMockRecorder) Authenticate(arg0 interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockPowerFlexClient)(nil).Authenticate), arg0) 51 | } 52 | 53 | // FindSystem mocks base method. 54 | func (m *MockPowerFlexClient) FindSystem(arg0, arg1, arg2 string) (*goscaleio.System, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "FindSystem", arg0, arg1, arg2) 57 | ret0, _ := ret[0].(*goscaleio.System) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // FindSystem indicates an expected call of FindSystem. 63 | func (mr *MockPowerFlexClientMockRecorder) FindSystem(arg0, arg1, arg2 interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindSystem", reflect.TypeOf((*MockPowerFlexClient)(nil).FindSystem), arg0, arg1, arg2) 66 | } 67 | 68 | // GetInstance mocks base method. 69 | func (m *MockPowerFlexClient) GetInstance(arg0 string) ([]*goscaleio0.System, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "GetInstance", arg0) 72 | ret0, _ := ret[0].([]*goscaleio0.System) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetInstance indicates an expected call of GetInstance. 78 | func (mr *MockPowerFlexClientMockRecorder) GetInstance(arg0 interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstance", reflect.TypeOf((*MockPowerFlexClient)(nil).GetInstance), arg0) 81 | } 82 | 83 | // GetStoragePool mocks base method. 84 | func (m *MockPowerFlexClient) GetStoragePool(arg0 string) ([]*goscaleio0.StoragePool, error) { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "GetStoragePool", arg0) 87 | ret0, _ := ret[0].([]*goscaleio0.StoragePool) 88 | ret1, _ := ret[1].(error) 89 | return ret0, ret1 90 | } 91 | 92 | // GetStoragePool indicates an expected call of GetStoragePool. 93 | func (mr *MockPowerFlexClientMockRecorder) GetStoragePool(arg0 interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStoragePool", reflect.TypeOf((*MockPowerFlexClient)(nil).GetStoragePool), arg0) 96 | } 97 | -------------------------------------------------------------------------------- /internal/service/mocks/powerflex_system_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: PowerFlexSystem) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | goscaleio "github.com/dell/goscaleio" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockPowerFlexSystem is a mock of PowerFlexSystem interface. 15 | type MockPowerFlexSystem struct { 16 | ctrl *gomock.Controller 17 | recorder *MockPowerFlexSystemMockRecorder 18 | } 19 | 20 | // MockPowerFlexSystemMockRecorder is the mock recorder for MockPowerFlexSystem. 21 | type MockPowerFlexSystemMockRecorder struct { 22 | mock *MockPowerFlexSystem 23 | } 24 | 25 | // NewMockPowerFlexSystem creates a new mock instance. 26 | func NewMockPowerFlexSystem(ctrl *gomock.Controller) *MockPowerFlexSystem { 27 | mock := &MockPowerFlexSystem{ctrl: ctrl} 28 | mock.recorder = &MockPowerFlexSystemMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockPowerFlexSystem) EXPECT() *MockPowerFlexSystemMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // FindSdc mocks base method. 38 | func (m *MockPowerFlexSystem) FindSdc(arg0, arg1 string) (*goscaleio.Sdc, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "FindSdc", arg0, arg1) 41 | ret0, _ := ret[0].(*goscaleio.Sdc) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // FindSdc indicates an expected call of FindSdc. 47 | func (mr *MockPowerFlexSystemMockRecorder) FindSdc(arg0, arg1 interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindSdc", reflect.TypeOf((*MockPowerFlexSystem)(nil).FindSdc), arg0, arg1) 50 | } 51 | -------------------------------------------------------------------------------- /internal/service/mocks/sdc_finder_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: SDCFinder) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockSDCFinder is a mock of SDCFinder interface. 14 | type MockSDCFinder struct { 15 | ctrl *gomock.Controller 16 | recorder *MockSDCFinderMockRecorder 17 | } 18 | 19 | // MockSDCFinderMockRecorder is the mock recorder for MockSDCFinder. 20 | type MockSDCFinderMockRecorder struct { 21 | mock *MockSDCFinder 22 | } 23 | 24 | // NewMockSDCFinder creates a new mock instance. 25 | func NewMockSDCFinder(ctrl *gomock.Controller) *MockSDCFinder { 26 | mock := &MockSDCFinder{ctrl: ctrl} 27 | mock.recorder = &MockSDCFinderMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockSDCFinder) EXPECT() *MockSDCFinderMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // GetSDCGuids mocks base method. 37 | func (m *MockSDCFinder) GetSDCGuids() ([]string, error) { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "GetSDCGuids") 40 | ret0, _ := ret[0].([]string) 41 | ret1, _ := ret[1].(error) 42 | return ret0, ret1 43 | } 44 | 45 | // GetSDCGuids indicates an expected call of GetSDCGuids. 46 | func (mr *MockSDCFinderMockRecorder) GetSDCGuids() *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSDCGuids", reflect.TypeOf((*MockSDCFinder)(nil).GetSDCGuids)) 49 | } 50 | -------------------------------------------------------------------------------- /internal/service/mocks/service_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: Service) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | service "github.com/amusingospre/karavi-metrics-powerflex/internal/service" 12 | gomock "github.com/golang/mock/gomock" 13 | v1 "k8s.io/api/core/v1" 14 | ) 15 | 16 | // MockService is a mock of Service interface. 17 | type MockService struct { 18 | ctrl *gomock.Controller 19 | recorder *MockServiceMockRecorder 20 | } 21 | 22 | // MockServiceMockRecorder is the mock recorder for MockService. 23 | type MockServiceMockRecorder struct { 24 | mock *MockService 25 | } 26 | 27 | // NewMockService creates a new mock instance. 28 | func NewMockService(ctrl *gomock.Controller) *MockService { 29 | mock := &MockService{ctrl: ctrl} 30 | mock.recorder = &MockServiceMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockService) EXPECT() *MockServiceMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // ExportVolumeStatistics mocks base method. 40 | func (m *MockService) ExportVolumeStatistics(arg0 context.Context, arg1 []service.VolumeStatisticsGetter, arg2 service.VolumeFinder) { 41 | m.ctrl.T.Helper() 42 | m.ctrl.Call(m, "ExportVolumeStatistics", arg0, arg1, arg2) 43 | } 44 | 45 | // ExportVolumeStatistics indicates an expected call of ExportVolumeStatistics. 46 | func (mr *MockServiceMockRecorder) ExportVolumeStatistics(arg0, arg1, arg2 interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportVolumeStatistics", reflect.TypeOf((*MockService)(nil).ExportVolumeStatistics), arg0, arg1, arg2) 49 | } 50 | 51 | // GetSDCStatistics mocks base method. 52 | func (m *MockService) GetSDCStatistics(arg0 context.Context, arg1 []v1.Node, arg2 []service.StatisticsGetter) { 53 | m.ctrl.T.Helper() 54 | m.ctrl.Call(m, "GetSDCStatistics", arg0, arg1, arg2) 55 | } 56 | 57 | // GetSDCStatistics indicates an expected call of GetSDCStatistics. 58 | func (mr *MockServiceMockRecorder) GetSDCStatistics(arg0, arg1, arg2 interface{}) *gomock.Call { 59 | mr.mock.ctrl.T.Helper() 60 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSDCStatistics", reflect.TypeOf((*MockService)(nil).GetSDCStatistics), arg0, arg1, arg2) 61 | } 62 | 63 | // GetSDCs mocks base method. 64 | func (m *MockService) GetSDCs(arg0 context.Context, arg1 service.PowerFlexClient, arg2 service.SDCFinder) ([]service.StatisticsGetter, error) { 65 | m.ctrl.T.Helper() 66 | ret := m.ctrl.Call(m, "GetSDCs", arg0, arg1, arg2) 67 | ret0, _ := ret[0].([]service.StatisticsGetter) 68 | ret1, _ := ret[1].(error) 69 | return ret0, ret1 70 | } 71 | 72 | // GetSDCs indicates an expected call of GetSDCs. 73 | func (mr *MockServiceMockRecorder) GetSDCs(arg0, arg1, arg2 interface{}) *gomock.Call { 74 | mr.mock.ctrl.T.Helper() 75 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSDCs", reflect.TypeOf((*MockService)(nil).GetSDCs), arg0, arg1, arg2) 76 | } 77 | 78 | // GetStorageClasses mocks base method. 79 | func (m *MockService) GetStorageClasses(arg0 context.Context, arg1 service.PowerFlexClient, arg2 service.StorageClassFinder) ([]service.StorageClassMeta, error) { 80 | m.ctrl.T.Helper() 81 | ret := m.ctrl.Call(m, "GetStorageClasses", arg0, arg1, arg2) 82 | ret0, _ := ret[0].([]service.StorageClassMeta) 83 | ret1, _ := ret[1].(error) 84 | return ret0, ret1 85 | } 86 | 87 | // GetStorageClasses indicates an expected call of GetStorageClasses. 88 | func (mr *MockServiceMockRecorder) GetStorageClasses(arg0, arg1, arg2 interface{}) *gomock.Call { 89 | mr.mock.ctrl.T.Helper() 90 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStorageClasses", reflect.TypeOf((*MockService)(nil).GetStorageClasses), arg0, arg1, arg2) 91 | } 92 | 93 | // GetStoragePoolStatistics mocks base method. 94 | func (m *MockService) GetStoragePoolStatistics(arg0 context.Context, arg1 []service.StorageClassMeta) { 95 | m.ctrl.T.Helper() 96 | m.ctrl.Call(m, "GetStoragePoolStatistics", arg0, arg1) 97 | } 98 | 99 | // GetStoragePoolStatistics indicates an expected call of GetStoragePoolStatistics. 100 | func (mr *MockServiceMockRecorder) GetStoragePoolStatistics(arg0, arg1 interface{}) *gomock.Call { 101 | mr.mock.ctrl.T.Helper() 102 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStoragePoolStatistics", reflect.TypeOf((*MockService)(nil).GetStoragePoolStatistics), arg0, arg1) 103 | } 104 | 105 | // GetVolumes mocks base method. 106 | func (m *MockService) GetVolumes(arg0 context.Context, arg1 []service.StatisticsGetter) ([]service.VolumeStatisticsGetter, error) { 107 | m.ctrl.T.Helper() 108 | ret := m.ctrl.Call(m, "GetVolumes", arg0, arg1) 109 | ret0, _ := ret[0].([]service.VolumeStatisticsGetter) 110 | ret1, _ := ret[1].(error) 111 | return ret0, ret1 112 | } 113 | 114 | // GetVolumes indicates an expected call of GetVolumes. 115 | func (mr *MockServiceMockRecorder) GetVolumes(arg0, arg1 interface{}) *gomock.Call { 116 | mr.mock.ctrl.T.Helper() 117 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumes", reflect.TypeOf((*MockService)(nil).GetVolumes), arg0, arg1) 118 | } 119 | -------------------------------------------------------------------------------- /internal/service/mocks/statistics_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: StatisticsGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | goscaleio "github.com/dell/goscaleio" 11 | goscaleio0 "github.com/dell/goscaleio/types/v1" 12 | gomock "github.com/golang/mock/gomock" 13 | ) 14 | 15 | // MockStatisticsGetter is a mock of StatisticsGetter interface. 16 | type MockStatisticsGetter struct { 17 | ctrl *gomock.Controller 18 | recorder *MockStatisticsGetterMockRecorder 19 | } 20 | 21 | // MockStatisticsGetterMockRecorder is the mock recorder for MockStatisticsGetter. 22 | type MockStatisticsGetterMockRecorder struct { 23 | mock *MockStatisticsGetter 24 | } 25 | 26 | // NewMockStatisticsGetter creates a new mock instance. 27 | func NewMockStatisticsGetter(ctrl *gomock.Controller) *MockStatisticsGetter { 28 | mock := &MockStatisticsGetter{ctrl: ctrl} 29 | mock.recorder = &MockStatisticsGetterMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockStatisticsGetter) EXPECT() *MockStatisticsGetterMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // FindVolumes mocks base method. 39 | func (m *MockStatisticsGetter) FindVolumes() ([]*goscaleio.Volume, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "FindVolumes") 42 | ret0, _ := ret[0].([]*goscaleio.Volume) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // FindVolumes indicates an expected call of FindVolumes. 48 | func (mr *MockStatisticsGetterMockRecorder) FindVolumes() *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindVolumes", reflect.TypeOf((*MockStatisticsGetter)(nil).FindVolumes)) 51 | } 52 | 53 | // GetStatistics mocks base method. 54 | func (m *MockStatisticsGetter) GetStatistics() (*goscaleio0.SdcStatistics, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "GetStatistics") 57 | ret0, _ := ret[0].(*goscaleio0.SdcStatistics) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // GetStatistics indicates an expected call of GetStatistics. 63 | func (mr *MockStatisticsGetterMockRecorder) GetStatistics() *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatistics", reflect.TypeOf((*MockStatisticsGetter)(nil).GetStatistics)) 66 | } 67 | 68 | // GetVolume mocks base method. 69 | func (m *MockStatisticsGetter) GetVolume() ([]*goscaleio0.Volume, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "GetVolume") 72 | ret0, _ := ret[0].([]*goscaleio0.Volume) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetVolume indicates an expected call of GetVolume. 78 | func (mr *MockStatisticsGetterMockRecorder) GetVolume() *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolume", reflect.TypeOf((*MockStatisticsGetter)(nil).GetVolume)) 81 | } 82 | -------------------------------------------------------------------------------- /internal/service/mocks/storage_class_finder_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: StorageClassFinder) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | v1 "k8s.io/api/storage/v1" 12 | ) 13 | 14 | // MockStorageClassFinder is a mock of StorageClassFinder interface. 15 | type MockStorageClassFinder struct { 16 | ctrl *gomock.Controller 17 | recorder *MockStorageClassFinderMockRecorder 18 | } 19 | 20 | // MockStorageClassFinderMockRecorder is the mock recorder for MockStorageClassFinder. 21 | type MockStorageClassFinderMockRecorder struct { 22 | mock *MockStorageClassFinder 23 | } 24 | 25 | // NewMockStorageClassFinder creates a new mock instance. 26 | func NewMockStorageClassFinder(ctrl *gomock.Controller) *MockStorageClassFinder { 27 | mock := &MockStorageClassFinder{ctrl: ctrl} 28 | mock.recorder = &MockStorageClassFinderMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockStorageClassFinder) EXPECT() *MockStorageClassFinderMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetStorageClasses mocks base method. 38 | func (m *MockStorageClassFinder) GetStorageClasses() ([]v1.StorageClass, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetStorageClasses") 41 | ret0, _ := ret[0].([]v1.StorageClass) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetStorageClasses indicates an expected call of GetStorageClasses. 47 | func (mr *MockStorageClassFinderMockRecorder) GetStorageClasses() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStorageClasses", reflect.TypeOf((*MockStorageClassFinder)(nil).GetStorageClasses)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/service/mocks/storage_pool_statistics_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: StoragePoolStatisticsGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | goscaleio "github.com/dell/goscaleio/types/v1" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockStoragePoolStatisticsGetter is a mock of StoragePoolStatisticsGetter interface. 15 | type MockStoragePoolStatisticsGetter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockStoragePoolStatisticsGetterMockRecorder 18 | } 19 | 20 | // MockStoragePoolStatisticsGetterMockRecorder is the mock recorder for MockStoragePoolStatisticsGetter. 21 | type MockStoragePoolStatisticsGetterMockRecorder struct { 22 | mock *MockStoragePoolStatisticsGetter 23 | } 24 | 25 | // NewMockStoragePoolStatisticsGetter creates a new mock instance. 26 | func NewMockStoragePoolStatisticsGetter(ctrl *gomock.Controller) *MockStoragePoolStatisticsGetter { 27 | mock := &MockStoragePoolStatisticsGetter{ctrl: ctrl} 28 | mock.recorder = &MockStoragePoolStatisticsGetterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockStoragePoolStatisticsGetter) EXPECT() *MockStoragePoolStatisticsGetterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetStatistics mocks base method. 38 | func (m *MockStoragePoolStatisticsGetter) GetStatistics() (*goscaleio.Statistics, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetStatistics") 41 | ret0, _ := ret[0].(*goscaleio.Statistics) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetStatistics indicates an expected call of GetStatistics. 47 | func (mr *MockStoragePoolStatisticsGetterMockRecorder) GetStatistics() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatistics", reflect.TypeOf((*MockStoragePoolStatisticsGetter)(nil).GetStatistics)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/service/mocks/volume_finder_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: VolumeFinder) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | k8s "github.com/amusingospre/karavi-metrics-powerflex/internal/k8s" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockVolumeFinder is a mock of VolumeFinder interface. 15 | type MockVolumeFinder struct { 16 | ctrl *gomock.Controller 17 | recorder *MockVolumeFinderMockRecorder 18 | } 19 | 20 | // MockVolumeFinderMockRecorder is the mock recorder for MockVolumeFinder. 21 | type MockVolumeFinderMockRecorder struct { 22 | mock *MockVolumeFinder 23 | } 24 | 25 | // NewMockVolumeFinder creates a new mock instance. 26 | func NewMockVolumeFinder(ctrl *gomock.Controller) *MockVolumeFinder { 27 | mock := &MockVolumeFinder{ctrl: ctrl} 28 | mock.recorder = &MockVolumeFinderMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockVolumeFinder) EXPECT() *MockVolumeFinderMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetPersistentVolumes mocks base method. 38 | func (m *MockVolumeFinder) GetPersistentVolumes() ([]k8s.VolumeInfo, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetPersistentVolumes") 41 | ret0, _ := ret[0].([]k8s.VolumeInfo) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetPersistentVolumes indicates an expected call of GetPersistentVolumes. 47 | func (mr *MockVolumeFinderMockRecorder) GetPersistentVolumes() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPersistentVolumes", reflect.TypeOf((*MockVolumeFinder)(nil).GetPersistentVolumes)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/service/mocks/volume_statistics_getter_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/amusingospre/karavi-metrics-powerflex/internal/service (interfaces: VolumeStatisticsGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | goscaleio "github.com/dell/goscaleio/types/v1" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockVolumeStatisticsGetter is a mock of VolumeStatisticsGetter interface. 15 | type MockVolumeStatisticsGetter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockVolumeStatisticsGetterMockRecorder 18 | } 19 | 20 | // MockVolumeStatisticsGetterMockRecorder is the mock recorder for MockVolumeStatisticsGetter. 21 | type MockVolumeStatisticsGetterMockRecorder struct { 22 | mock *MockVolumeStatisticsGetter 23 | } 24 | 25 | // NewMockVolumeStatisticsGetter creates a new mock instance. 26 | func NewMockVolumeStatisticsGetter(ctrl *gomock.Controller) *MockVolumeStatisticsGetter { 27 | mock := &MockVolumeStatisticsGetter{ctrl: ctrl} 28 | mock.recorder = &MockVolumeStatisticsGetterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockVolumeStatisticsGetter) EXPECT() *MockVolumeStatisticsGetterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetVolumeStatistics mocks base method. 38 | func (m *MockVolumeStatisticsGetter) GetVolumeStatistics() (*goscaleio.VolumeStatistics, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetVolumeStatistics") 41 | ret0, _ := ret[0].(*goscaleio.VolumeStatistics) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetVolumeStatistics indicates an expected call of GetVolumeStatistics. 47 | func (mr *MockVolumeStatisticsGetterMockRecorder) GetVolumeStatistics() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumeStatistics", reflect.TypeOf((*MockVolumeStatisticsGetter)(nil).GetVolumeStatistics)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/service/testdata/config-empty-file.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amusingospre/karavi-metrics-powerflex/6ad0e4d1a61a71f855462608f4891dafb06ca9e6/internal/service/testdata/config-empty-file.json -------------------------------------------------------------------------------- /internal/service/testdata/config-invalid-format.json: -------------------------------------------------------------------------------- 1 | Not valid json 2 | -------------------------------------------------------------------------------- /internal/service/testdata/config-missing-endpoint.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "admin", 4 | "password": "password", 5 | "systemID": "ID1", 6 | "insecure": true, 7 | "mdm": "mdm1,mdm2" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /internal/service/testdata/config-missing-password.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "admin", 4 | "systemID": "ID1", 5 | "endpoint": "http://127.0.0.1", 6 | "insecure": true, 7 | "mdm": "mdm1,mdm2" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /internal/service/testdata/config-missing-systemid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "admin", 4 | "password": "password", 5 | "endpoint": "http://127.0.0.1", 6 | "insecure": true, 7 | "mdm": "mdm1,mdm2" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /internal/service/testdata/config-missing-username.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "password": "password", 4 | "systemID": "ID1", 5 | "endpoint": "http://127.0.0.1", 6 | "insecure": true, 7 | "mdm": "mdm1,mdm2" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /internal/service/testdata/config-with-0-storage-systems.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /internal/service/testdata/config-with-invalid-default-system.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "admin", 4 | "password": "password", 5 | "systemID": "ID1", 6 | "endpoint": "http://127.0.0.1", 7 | "insecure": true, 8 | "mdm": "mdm1,mdm2" 9 | }, 10 | { 11 | "isDefault": true 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /internal/service/testdata/config-with-no-default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "admin", 4 | "password": "password", 5 | "systemID": "ID1", 6 | "endpoint": "http://127.0.0.1", 7 | "insecure": true, 8 | "mdm": "mdm1,mdm2" 9 | }, 10 | { 11 | "username": "admin", 12 | "password": "password", 13 | "systemID": "ID2", 14 | "endpoint": "https://127.0.0.2", 15 | "insecure": true, 16 | "mdm": "mdm3,mdm4" 17 | 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /internal/service/testdata/config-with-no-default.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - username: admin 3 | password: password 4 | systemID: ID1 5 | endpoint: http://127.0.0.1 6 | insecure: true 7 | mdm: mdm1,mdm2 8 | - username: admin 9 | password: password 10 | systemID: ID2 11 | endpoint: https://127.0.0.2 12 | insecure: true 13 | mdm: mdm3,mdm4 14 | -------------------------------------------------------------------------------- /internal/service/testdata/config-with-skipCertificateValidation.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - username: admin 3 | password: password 4 | systemID: ID1 5 | endpoint: http://127.0.0.1 6 | skipCertificateValidation: true 7 | mdm: mdm1,mdm2 8 | - username: admin 9 | password: password 10 | systemID: ID2 11 | endpoint: https://127.0.0.2 12 | skipCertificateValidation: false 13 | mdm: mdm3,mdm4 14 | -------------------------------------------------------------------------------- /internal/service/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package service 18 | 19 | // MappedSDC is the summerized details of the SDCs volume is mapped to 20 | type MappedSDC struct { 21 | SdcID string `json:"sdcId"` 22 | SdcIP string `json:"sdcIp"` 23 | } 24 | 25 | // VolumeMeta is the details of a volume in an SDC 26 | type VolumeMeta struct { 27 | ID string 28 | Name string 29 | PersistentVolumeName string 30 | PersistentVolumeClaimName string 31 | Namespace string 32 | StorageSystemID string 33 | MappedSDCs []MappedSDC 34 | } 35 | 36 | // SDCMeta is meta data for a specific SDC 37 | type SDCMeta struct { 38 | ID string 39 | Name string 40 | IP string 41 | SdcGUID string 42 | } 43 | 44 | // StorageClassInfo is meta data about a storage class and contains the associated PowerFlex storage pool names 45 | type StorageClassInfo struct { 46 | ID string 47 | Name string 48 | Driver string 49 | StorageSystemID string 50 | StoragePools []string 51 | } 52 | 53 | // StorageClassMeta is the same as StorageClassInfo except it contains a map of PowerFlex storage pool IDs to goscaleio storage pool structs 54 | type StorageClassMeta struct { 55 | ID string 56 | Name string 57 | Driver string 58 | StorageSystemID string 59 | StoragePools map[string]StoragePoolStatisticsGetter 60 | } 61 | -------------------------------------------------------------------------------- /licenses/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /opentelemetry/exporters/mocks/otlexporters_mocks.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by MockGen. DO NOT EDIT. 18 | // Source: github.com/amusingospre/karavi-metrics-powerflex/opentelemetry/exporters (interfaces: Otlexporter) 19 | 20 | // Package exportermocks is a generated GoMock package. 21 | package exportermocks 22 | 23 | import ( 24 | reflect "reflect" 25 | 26 | gomock "github.com/golang/mock/gomock" 27 | otlpmetricgrpc "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" 28 | ) 29 | 30 | // MockOtlexporter is a mock of Otlexporter interface. 31 | type MockOtlexporter struct { 32 | ctrl *gomock.Controller 33 | recorder *MockOtlexporterMockRecorder 34 | } 35 | 36 | // MockOtlexporterMockRecorder is the mock recorder for MockOtlexporter. 37 | type MockOtlexporterMockRecorder struct { 38 | mock *MockOtlexporter 39 | } 40 | 41 | // NewMockOtlexporter creates a new mock instance. 42 | func NewMockOtlexporter(ctrl *gomock.Controller) *MockOtlexporter { 43 | mock := &MockOtlexporter{ctrl: ctrl} 44 | mock.recorder = &MockOtlexporterMockRecorder{mock} 45 | return mock 46 | } 47 | 48 | // EXPECT returns an object that allows the caller to indicate expected use. 49 | func (m *MockOtlexporter) EXPECT() *MockOtlexporterMockRecorder { 50 | return m.recorder 51 | } 52 | 53 | // InitExporter mocks base method. 54 | func (m *MockOtlexporter) InitExporter(arg0 ...otlpmetricgrpc.Option) error { 55 | m.ctrl.T.Helper() 56 | varargs := []interface{}{} 57 | for _, a := range arg0 { 58 | varargs = append(varargs, a) 59 | } 60 | ret := m.ctrl.Call(m, "InitExporter", varargs...) 61 | ret0, _ := ret[0].(error) 62 | return ret0 63 | } 64 | 65 | // InitExporter indicates an expected call of InitExporter. 66 | func (mr *MockOtlexporterMockRecorder) InitExporter(arg0 ...interface{}) *gomock.Call { 67 | mr.mock.ctrl.T.Helper() 68 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitExporter", reflect.TypeOf((*MockOtlexporter)(nil).InitExporter), arg0...) 69 | } 70 | 71 | // StopExporter mocks base method. 72 | func (m *MockOtlexporter) StopExporter() error { 73 | m.ctrl.T.Helper() 74 | ret := m.ctrl.Call(m, "StopExporter") 75 | ret0, _ := ret[0].(error) 76 | return ret0 77 | } 78 | 79 | // StopExporter indicates an expected call of StopExporter. 80 | func (mr *MockOtlexporterMockRecorder) StopExporter() *gomock.Call { 81 | mr.mock.ctrl.T.Helper() 82 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopExporter", reflect.TypeOf((*MockOtlexporter)(nil).StopExporter)) 83 | } 84 | -------------------------------------------------------------------------------- /opentelemetry/exporters/opentelemetry_collector_exporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package otlexporters 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | "go.opentelemetry.io/otel" 24 | "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" 25 | "go.opentelemetry.io/otel/sdk/metric" 26 | ) 27 | 28 | // OtlCollectorExporter is the exporter for the OpenTelemetry Collector 29 | type OtlCollectorExporter struct { 30 | CollectorAddr string 31 | exporter *otlpmetricgrpc.Exporter 32 | controller *metric.MeterProvider 33 | } 34 | 35 | const ( 36 | // DefaultCollectorCertPath is the default location to look for the Collector certificate 37 | DefaultCollectorCertPath = "/etc/ssl/certs/cert.crt" 38 | ) 39 | 40 | // InitExporter is the initialization method for the OpenTelemetry Collector exporter 41 | func (c *OtlCollectorExporter) InitExporter(opts ...otlpmetricgrpc.Option) error { 42 | exporter, controller, err := c.initOTLPExporter(opts...) 43 | if err != nil { 44 | return err 45 | } 46 | c.exporter = exporter 47 | c.controller = controller 48 | 49 | return err 50 | } 51 | 52 | // StopExporter stops the activity of the Otl Collector's required services 53 | func (c *OtlCollectorExporter) StopExporter() error { 54 | err := c.exporter.Shutdown(context.Background()) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | err = c.controller.Shutdown(context.Background()) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func (c *OtlCollectorExporter) initOTLPExporter(opts ...otlpmetricgrpc.Option) (*otlpmetricgrpc.Exporter, *metric.MeterProvider, error) { 68 | exporter, err := otlpmetricgrpc.New(context.Background(), opts...) 69 | if err != nil { 70 | return nil, nil, err 71 | } 72 | 73 | meterProvider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exporter, metric.WithInterval(5*time.Second)))) 74 | 75 | otel.SetMeterProvider(meterProvider) 76 | 77 | return exporter, meterProvider, nil 78 | } 79 | -------------------------------------------------------------------------------- /opentelemetry/exporters/opentelemetry_collector_exporter_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package otlexporters 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" 26 | ) 27 | 28 | func TestInitExporter(t *testing.T) { 29 | tests := []struct { 30 | name string 31 | collector *OtlCollectorExporter 32 | opts []otlpmetricgrpc.Option 33 | ExpectedError error 34 | }{ 35 | { 36 | name: "Successful Exporter Initialization", 37 | collector: &OtlCollectorExporter{ 38 | CollectorAddr: "localhost:8080", 39 | }, 40 | opts: []otlpmetricgrpc.Option{ 41 | otlpmetricgrpc.WithInsecure(), 42 | }, 43 | ExpectedError: nil, 44 | }, 45 | { 46 | name: "Invalid Service Config", 47 | collector: &OtlCollectorExporter{ 48 | CollectorAddr: "localhost:8080", 49 | }, 50 | opts: []otlpmetricgrpc.Option{ 51 | otlpmetricgrpc.WithServiceConfig("invalid config"), 52 | }, 53 | ExpectedError: errors.New("grpc: the provided default service config is invalid: invalid character 'i' looking for beginning of value"), 54 | }, 55 | } 56 | 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | err := tt.collector.InitExporter(tt.opts...) 60 | assert.Equal(t, err, tt.ExpectedError) 61 | }) 62 | } 63 | } 64 | 65 | func TestOtlCollectorExporter_StopExporter(t *testing.T) { 66 | tests := []struct { 67 | name string 68 | collector *OtlCollectorExporter 69 | opts []otlpmetricgrpc.Option 70 | preShutdown bool 71 | ExpectedError error 72 | }{ 73 | { 74 | name: "Error: gRPC exporter is shutdown", 75 | collector: &OtlCollectorExporter{ 76 | CollectorAddr: "localhost:8080", 77 | }, 78 | opts: []otlpmetricgrpc.Option{ 79 | otlpmetricgrpc.WithInsecure(), 80 | }, 81 | preShutdown: true, 82 | ExpectedError: errors.New("gRPC exporter is shutdown"), 83 | }, 84 | { 85 | name: "Shutdown Exporter", 86 | collector: &OtlCollectorExporter{ 87 | CollectorAddr: "localhost:8080", 88 | }, 89 | opts: []otlpmetricgrpc.Option{ 90 | otlpmetricgrpc.WithInsecure(), 91 | otlpmetricgrpc.WithEndpoint("localhost:8080"), 92 | }, 93 | preShutdown: false, 94 | ExpectedError: errors.New("gRPC exporter is shutdown"), 95 | }, 96 | } 97 | 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | err := tt.collector.InitExporter(tt.opts...) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | if tt.preShutdown { 106 | _ = tt.collector.exporter.Shutdown(context.Background()) 107 | } 108 | 109 | err = tt.collector.StopExporter() 110 | if err != nil && tt.ExpectedError == nil { 111 | t.Fatal(err) 112 | } 113 | }) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /opentelemetry/exporters/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2022 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package otlexporters 18 | 19 | import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" 20 | 21 | // Otlexporter is an interface for all OpenTelemetry exporters 22 | // 23 | //go:generate mockgen -destination=mocks/otlexporters_mocks.go -package=exportermocks github.com/amusingospre/karavi-metrics-powerflex/opentelemetry/exporters Otlexporter 24 | type Otlexporter interface { 25 | InitExporter(...otlpmetricgrpc.Option) error 26 | StopExporter() error 27 | } 28 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CHECK_DIRS="$*" 4 | 5 | if [ -f "../vendor" ]; then 6 | # Tell the applicable Go tools to use the vendor directory, if it exists. 7 | MOD_FLAGS="-mod=vendor" 8 | fi 9 | FMT_TMPFILE=/tmp/check_fmt 10 | FMT_COUNT_TMPFILE=${FMT_TMPFILE}.count 11 | 12 | fmt_count() { 13 | if [ ! -f $FMT_COUNT_TMPFILE ]; then 14 | echo "0" 15 | fi 16 | 17 | head -1 $FMT_COUNT_TMPFILE 18 | } 19 | 20 | fmt() { 21 | gofmt -d ${CHECK_DIRS//...} | tee $FMT_TMPFILE 22 | cat $FMT_TMPFILE | wc -l > $FMT_COUNT_TMPFILE 23 | if [ ! `cat $FMT_COUNT_TMPFILE` -eq "0" ]; then 24 | echo Found `cat $FMT_COUNT_TMPFILE` formatting issue\(s\). 25 | return 1 26 | fi 27 | } 28 | 29 | echo === Checking format... 30 | fmt 31 | FMT_RETURN_CODE=$? 32 | echo === Finished 33 | 34 | echo === Vetting... 35 | go vet ${MOD_FLAGS} ${CHECK_DIRS} 36 | VET_RETURN_CODE=$? 37 | echo === Finished 38 | 39 | echo === Linting... 40 | (command -v golint >/dev/null 2>&1 \ 41 | || GO111MODULE=off go get -insecure -u golang.org/x/lint/golint) \ 42 | && golint --set_exit_status ${CHECK_DIRS} 43 | LINT_RETURN_CODE=$? 44 | echo === Finished 45 | 46 | echo === Running gosec... 47 | gosec -quiet ${MOD_FLAGS} ${CHECK_DIRS} 48 | SEC_RETURN_CODE=$? 49 | echo === Finished 50 | 51 | # Report output. 52 | fail_checks=0 53 | [ "${FMT_RETURN_CODE}" != "0" ] && echo "Formatting checks failed! => Run 'make format'." && fail_checks=1 54 | [ "${VET_RETURN_CODE}" != "0" ] && echo "Vetting checks failed!" && fail_checks=1 55 | [ "${LINT_RETURN_CODE}" != "0" ] && echo "Linting checks failed!" && fail_checks=1 56 | [ "${SEC_RETURN_CODE}" != "0" ] && echo "Security checks failed!" && fail_checks=1 57 | 58 | exit ${fail_checks} --------------------------------------------------------------------------------