From 9d8d2fdcdf8771acbc21d3c6a435773a6c54ad65 Mon Sep 17 00:00:00 2001 From: Abhinandan Purkait Date: Thu, 21 Mar 2024 14:46:31 +0530 Subject: [PATCH] revert: remove legacy engines from the flow (#157) Signed-off-by: Abhinandan Purkait --- .github/workflows/go.yml | 20 +- .github/workflows/golangci-lint.yml | 6 +- .github/workflows/release.yml | 11 +- .github/workflows/test-coverage.yml | 10 +- .tours/first.tour | 72 -- README.md | 39 +- cmd/clusterinfo/cluster-info.go | 9 - cmd/completion/completion.go | 4 +- cmd/describe/describe.go | 29 +- cmd/describe/pvc.go | 32 +- cmd/describe/storage.go | 22 +- cmd/describe/volume.go | 19 +- cmd/generate/generate.go | 93 -- cmd/get/blockdevice.go | 51 - cmd/get/get.go | 28 - cmd/get/storage.go | 22 +- cmd/get/volume.go | 21 +- cmd/openebs.go | 43 +- cmd/upgrade/status.go | 35 - cmd/upgrade/upgrade.go | 73 -- cmd/version/version.go | 22 +- docs/cstor/README.md | 272 ----- docs/cstor/img.png | Bin 175223 -> 0 bytes docs/jiva/README.md | 90 -- docs/localpv-lvm/README.md | 34 +- go.mod | 95 +- go.sum | 601 ++++++----- pkg/blockdevice/blockdevice.go | 96 -- pkg/blockdevice/blockdevice_test.go | 202 ---- pkg/blockdevice/testdata_test.go | 82 -- pkg/client/bd.go | 92 -- pkg/client/cstor.go | 294 ------ pkg/client/jiva.go | 111 -- pkg/client/k8s.go | 162 +-- pkg/client/lvmlocalpv.go | 2 +- pkg/clusterinfo/cluster-info.go | 51 +- pkg/clusterinfo/cluster-info_test.go | 602 ----------- pkg/clusterinfo/testdata_test.go | 200 ---- pkg/generate/cspc.go | 299 ------ pkg/generate/cspc_test.go | 248 ----- pkg/generate/sort.go | 102 -- pkg/generate/sort_test.go | 203 ---- pkg/generate/testdata_test.go | 440 -------- pkg/persistentvolumeclaim/cstor.go | 153 --- pkg/persistentvolumeclaim/cstor_test.go | 132 --- pkg/persistentvolumeclaim/debug.go | 76 -- pkg/persistentvolumeclaim/debug_cstor.go | 363 ------- pkg/persistentvolumeclaim/debug_cstor_test.go | 964 ------------------ pkg/persistentvolumeclaim/generic_test.go | 6 +- pkg/persistentvolumeclaim/jiva.go | 154 --- pkg/persistentvolumeclaim/jiva_test.go | 47 - .../persistentvolumeclaim.go | 14 +- pkg/persistentvolumeclaim/testdata_test.go | 741 -------------- pkg/storage/cstor.go | 147 --- pkg/storage/cstor_test.go | 169 --- pkg/storage/lvmlocalpv.go | 2 +- pkg/storage/lvmlocalpv_test.go | 9 +- pkg/storage/storage.go | 18 +- pkg/storage/testdata_test.go | 159 --- pkg/storage/zfslocalpv.go | 1 + pkg/storage/zfslocalpv_test.go | 2 +- pkg/upgrade/api.go | 85 -- pkg/upgrade/cstor.go | 165 --- pkg/upgrade/jiva.go | 174 ---- pkg/upgrade/status/jiva.go | 79 -- pkg/upgrade/upgrade.go | 206 ---- pkg/util/checks.go | 12 +- pkg/util/checks_test.go | 31 - pkg/util/constant.go | 171 ---- pkg/util/error_test.go | 4 +- pkg/util/k8s_utils.go | 13 +- pkg/util/k8s_utils_test.go | 220 +--- pkg/util/testdata_test.go | 77 -- pkg/util/types.go | 144 +-- pkg/util/util.go | 4 +- pkg/volume/cstor.go | 254 ----- pkg/volume/cstor_test.go | 285 ------ pkg/volume/jiva.go | 193 ---- pkg/volume/jiva_test.go | 75 -- pkg/volume/local_hostpath_test.go | 11 +- pkg/volume/lvmlocalpv_test.go | 17 +- pkg/volume/testdata_test.go | 543 ---------- pkg/volume/volume.go | 6 +- pkg/volume/volume_test.go | 2 +- pkg/volume/zfs_localpv_test.go | 21 +- 85 files changed, 604 insertions(+), 10284 deletions(-) delete mode 100644 .tours/first.tour delete mode 100644 cmd/generate/generate.go delete mode 100644 cmd/get/blockdevice.go delete mode 100644 cmd/upgrade/status.go delete mode 100644 cmd/upgrade/upgrade.go delete mode 100644 docs/cstor/README.md delete mode 100644 docs/cstor/img.png delete mode 100644 docs/jiva/README.md delete mode 100644 pkg/blockdevice/blockdevice.go delete mode 100644 pkg/blockdevice/blockdevice_test.go delete mode 100644 pkg/blockdevice/testdata_test.go delete mode 100644 pkg/client/bd.go delete mode 100644 pkg/client/cstor.go delete mode 100644 pkg/client/jiva.go delete mode 100644 pkg/clusterinfo/cluster-info_test.go delete mode 100644 pkg/clusterinfo/testdata_test.go delete mode 100644 pkg/generate/cspc.go delete mode 100644 pkg/generate/cspc_test.go delete mode 100644 pkg/generate/sort.go delete mode 100644 pkg/generate/sort_test.go delete mode 100644 pkg/generate/testdata_test.go delete mode 100644 pkg/persistentvolumeclaim/cstor.go delete mode 100644 pkg/persistentvolumeclaim/cstor_test.go delete mode 100644 pkg/persistentvolumeclaim/debug.go delete mode 100644 pkg/persistentvolumeclaim/debug_cstor.go delete mode 100644 pkg/persistentvolumeclaim/debug_cstor_test.go delete mode 100644 pkg/persistentvolumeclaim/jiva.go delete mode 100644 pkg/persistentvolumeclaim/jiva_test.go delete mode 100644 pkg/storage/cstor.go delete mode 100644 pkg/storage/cstor_test.go delete mode 100644 pkg/upgrade/api.go delete mode 100644 pkg/upgrade/cstor.go delete mode 100644 pkg/upgrade/jiva.go delete mode 100644 pkg/upgrade/status/jiva.go delete mode 100644 pkg/upgrade/upgrade.go delete mode 100644 pkg/util/testdata_test.go delete mode 100644 pkg/volume/cstor.go delete mode 100644 pkg/volume/cstor_test.go delete mode 100644 pkg/volume/jiva.go delete mode 100644 pkg/volume/jiva_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f31af49a..effe6b9d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,14 +6,14 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.18.3 - uses: actions/setup-go@v2 + - name: Set up Go 1.19 + uses: actions/setup-go@v4 with: - go-version: 1.18.3 - id: go_3 + go-version: 1.19.9 + cache: false - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Run Deps Check run: make verify-deps @@ -23,14 +23,14 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.18.3 - uses: actions/setup-go@v2 + - name: Set up Go 1.19 + uses: actions/setup-go@v4 with: - go-version: 1.18.3 - id: go + go-version: 1.19.9 + cache: false - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Build Openebsctl run: make openebsctl diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 5843e880..047d4c49 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -11,8 +11,8 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v4 with: - version: v1.46.2 + version: v1.54 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8eed1ff3..092066e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,15 +15,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v2 + - name: Set up Go 1.19 + uses: actions/setup-go@v4 with: - go-version: 1.18.3 + go-version: 1.19.9 + cache: false - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser version: latest diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 5451fde0..93e81f1f 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -10,13 +10,15 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 2 - - uses: actions/setup-go@v2 + - name: Set up Go 1.19 + uses: actions/setup-go@v4 with: - go-version: '1.17.7' + go-version: 1.19.9 + cache: false - name: Run coverage run: go test ./... --coverprofile=coverage.out --covermode=atomic - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 diff --git a/.tours/first.tour b/.tours/first.tour deleted file mode 100644 index dd2c2e72..00000000 --- a/.tours/first.tour +++ /dev/null @@ -1,72 +0,0 @@ -{ - "$schema": "https://aka.ms/codetour-schema", - "title": "tour", - "steps": [ - { - "file": "cmd/openebs.go", - "description": "This function gets called when the binary runs via kubectl or directly, read about kubectl plugins or watch this [Kubecon talk](https://www.youtube.com/watch?v=83ITOTsXsHU)", - "line": 66 - }, - { - "file": "cmd/get/get.go", - "description": "The CLI supports getting these resources, think of them as the nouns the CLI can get(verb). Read about [spf13/cobra](https://github.com/spf13/cobra) to understand more", - "line": 53 - }, - { - "file": "cmd/get/blockdevice.go", - "description": "This function is called when `kubectl openebs get bd` is run", - "line": 46 - }, - { - "file": "cmd/get/get.go", - "description": "Each command can have some local or global flags.", - "line": 56 - }, - { - "file": "cmd/get/storage.go", - "description": "This function is called when user runs `kubectl openebs get storage`", - "line": 49 - }, - { - "file": "pkg/storage/storage.go", - "description": "If the storage(pools) of a well defined `casType` are requested, only that function, `f` is called, this happens when user runs something like `kubectl openebs get storage --cas-type=cstor`.", - "line": 34 - }, - { - "file": "pkg/storage/storage.go", - "description": "When no cas-type is specified, each Storage Engines' storage(pool) is listed", - "line": 49 - }, - { - "file": "pkg/storage/storage.go", - "description": "While some or all of `storage` conceptual resource can be listed, they can also be described individually, this function handles that.", - "line": 76 - }, - { - "file": "cmd/describe/volume.go", - "description": "Like storage, volumes can be described too", - "line": 49 - }, - { - "file": "cmd/clusterinfo/cluster-info.go", - "description": "Besides listing information about the cluster's storage resources of storage engines, the CLI can also identify which storage components are installed on the current cluster & can offer some version and health information.", - "line": 39 - }, - { - "file": "cmd/upgrade/upgrade.go", - "description": "The CLI can also schedule jobs to trigger data plane upgrades of the storage components, right now only Jiva and some upgrade features of cstor are supported.", - "line": 56 - }, - { - "file": "pkg/volume/cstor.go", - "description": "The logic for showing volumes are in the pkg/volumes package, all code is seggregated by the storage engine named filename.", - "line": 62 - }, - { - "file": "docs/cstor/README.md", - "description": "When a new feature for a storage engine is added, it's usually documented here.", - "line": 5 - } - ], - "ref": "codewalk" -} \ No newline at end of file diff --git a/README.md b/README.md index 216e2592..2e016e10 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ OpenEBSCTL is a kubectl plugin to manage OpenEBS storage components. ## Project Status **Alpha**. Under active development and seeking [contributions from the community](#contributing). -The CLI currently supports managing `cStor`, `Jiva`, `LocalPV-LVM`, `LocalPV-ZFS` Cas-Engines. +The CLI currently supports managing `LocalPV-LVM`, `LocalPV-ZFS` and `LocalPV-HostPath` Engines. ## Table of Contents * [Installation](#installation) @@ -59,44 +59,33 @@ OpenEBSCTL is available on Linux, macOS and Windows platforms. - `cd openebsctl` - Run `make openebsctl` -## Code Walkthrough - -1. Install [vscode](https://code.visualstudio.com/) -2. Install [CodeTour plugin](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) on vscode -3. Open this project on vscode & press `[ctrl] + [shift] + [p]` or `[command] + [shift] + [p]` and click `CodeTour: Open The Tour File` and locate the appropriate `*.tour` file. The code walkthrough will begin. Happy Contributing! - ## Usage * ```bash $ kubectl openebs - openebs is a a kubectl plugin for interacting with OpenEBS storage components such as storage(pools, volumegroups), volumes, blockdevices, pvcs. + kubectl openebs is a a kubectl plugin for interacting with OpenEBS storage components such as storage(zfspools, volumegroups), volumes, pvcs. Find out more about OpenEBS on https://openebs.io/ - + Usage: - kubectl openebs [command] [resource] [...names] [flags] + openebs [command] Available Commands: - completion Outputs shell completion code for the specified shell (bash or zsh) - describe Provide detailed information about an OpenEBS resource - get Provides fetching operations related to a Volume/Pool - help Help about any command - version Shows openebs kubectl plugin's version + cluster-info Show component version, status and running components for each installed engine + completion Outputs shell completion code for the specified shell (bash or zsh) + describe Provide detailed information about an OpenEBS resource + get Provides fetching operations related to a Volume/Storage + help Help about any command + version Shows openebs kubectl plugin's version Flags: - -h, --help help for openebs - -n, --namespace string If present, the namespace scope for this CLI request - --openebs-namespace string to read the openebs namespace from user. - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. - --debug to launch the debugging mode for cstor pvcs. + -h, --help help for openebs + -c, --kubeconfig string path to config file + -v, --version version for openebs - Use "kubectl openebs command --help" for more information about a command. + Use "openebs [command] --help" for more information about a command. ``` * To know more about various engine specific commands check these:- - * [cStor](docs/cstor/README.md) - * [Jiva](docs/jiva/README.md) * [LocalPV-LVM](docs/localpv-lvm/README.md) * [LocalPV-ZFS](docs/localpv-zfs/README.md) diff --git a/cmd/clusterinfo/cluster-info.go b/cmd/clusterinfo/cluster-info.go index 9a74eca8..3962e4ce 100644 --- a/cmd/clusterinfo/cluster-info.go +++ b/cmd/clusterinfo/cluster-info.go @@ -22,14 +22,6 @@ import ( "github.com/spf13/cobra" ) -const ( - clusterInfoCmdHelp = `Usage: - kubectl openebs cluster-info -Flags: - -h, --help help for openebs get command -` -) - // NewCmdClusterInfo shows OpenEBSCTL cluster-info func NewCmdClusterInfo(rootCmd *cobra.Command) *cobra.Command { cmd := &cobra.Command{ @@ -39,6 +31,5 @@ func NewCmdClusterInfo(rootCmd *cobra.Command) *cobra.Command { util.CheckErr(clusterinfo.ShowClusterInfo(), util.Fatal) }, } - cmd.SetUsageTemplate(clusterInfoCmdHelp) return cmd } diff --git a/cmd/completion/completion.go b/cmd/completion/completion.go index a97d0aca..3ef59288 100644 --- a/cmd/completion/completion.go +++ b/cmd/completion/completion.go @@ -21,7 +21,7 @@ import ( "os" "github.com/spf13/cobra" - "k8s.io/klog" + "k8s.io/klog/v2" ) const ( @@ -49,7 +49,7 @@ func NewCmdCompletion(rootCmd *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "completion", ValidArgs: []string{"bash", "zsh"}, - Args: cobra.ExactValidArgs(1), + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Short: "Outputs shell completion code for the specified shell (bash or zsh)", PersistentPreRun: func(cmd *cobra.Command, args []string) { }, diff --git a/cmd/describe/describe.go b/cmd/describe/describe.go index 57b905dc..2cdfaf81 100644 --- a/cmd/describe/describe.go +++ b/cmd/describe/describe.go @@ -20,37 +20,11 @@ import ( "github.com/spf13/cobra" ) -const ( - volumeCommandHelpText = `Show detailed description of a specific OpenEBS resource: - -Usage: - kubectl openebs describe [volume|storage|pvc] [...names] [flags] - -Describe a Volume: - kubectl openebs describe volume [...names] [flags] - -Describe PVCs present in the same namespace: - kubectl openebs describe pvc [...names] [flags] - -Describe a Storage : - kubectl openebs describe storage [...names] [flags] - -Flags: - -h, --help help for openebs - -n, --namespace string to read the namespace for the pvc. - --openebs-namespace string to read the openebs namespace from user. - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. - --debug to launch the debugging mode for cstor pvcs. -` -) - // NewCmdDescribe provides options for managing OpenEBS Volume func NewCmdDescribe(rootCmd *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "describe", - ValidArgs: []string{"pool", "volume", "pvc"}, + ValidArgs: []string{"storage", "volume", "pvc"}, Short: "Provide detailed information about an OpenEBS resource", } cmd.AddCommand( @@ -58,6 +32,5 @@ func NewCmdDescribe(rootCmd *cobra.Command) *cobra.Command { NewCmdDescribePVC(), NewCmdDescribeStorage(), ) - cmd.SetUsageTemplate(volumeCommandHelpText) return cmd } diff --git a/cmd/describe/pvc.go b/cmd/describe/pvc.go index 1d603b5c..e8b2c799 100644 --- a/cmd/describe/pvc.go +++ b/cmd/describe/pvc.go @@ -22,27 +22,10 @@ import ( "github.com/spf13/cobra" ) -var ( - pvcInfoCommandHelpText = `This command fetches information and status of the various aspects -of the PersistentVolumeClaims and its underlying related resources -in the provided namespace. If no namespace is provided it uses default -namespace for execution. - -Usage: - kubectl openebs describe pvc [...names] [flags] - -Flags: - -h, --help help for openebs - -n, --namespace string to read the namespace for the pvc. - --openebs-namespace string to read the openebs namespace from user. - If not provided it is determined from components. - --debug to launch the debugging mode for cstor pvcs. -` -) - // NewCmdDescribePVC Displays the pvc describe details func NewCmdDescribePVC() *cobra.Command { - var debug bool + var openebsNs string + var pvNs string cmd := &cobra.Command{ Use: "pvc", Aliases: []string{"pvcs", "persistentvolumeclaims", "persistentvolumeclaim"}, @@ -53,15 +36,10 @@ func NewCmdDescribePVC() *cobra.Command { pvNs = "default" } openebsNamespace, _ = cmd.Flags().GetString("openebs-namespace") - if debug { - util.CheckErr(persistentvolumeclaim.Debug(args, pvNs, openebsNamespace), util.Fatal) - } else { - util.CheckErr(persistentvolumeclaim.Describe(args, pvNs, openebsNamespace), util.Fatal) - } - + util.CheckErr(persistentvolumeclaim.Describe(args, pvNs, openebsNamespace), util.Fatal) }, } - cmd.SetUsageTemplate(pvcInfoCommandHelpText) - cmd.Flags().BoolVar(&debug, "debug", false, "Debug cstor volume") + cmd.PersistentFlags().StringVarP(&openebsNs, "openebs-namespace", "", "", "to read the openebs namespace from user.\nIf not provided it is determined from components.") + cmd.PersistentFlags().StringVarP(&pvNs, "namespace", "n", "", "to read the namespace of the pvc from the user. If not provided defaults to default namespace.") return cmd } diff --git a/cmd/describe/storage.go b/cmd/describe/storage.go index c75604a4..49987b5c 100644 --- a/cmd/describe/storage.go +++ b/cmd/describe/storage.go @@ -17,6 +17,7 @@ limitations under the License. package describe import ( + "fmt" "strings" "github.com/openebs/openebsctl/pkg/storage" @@ -24,25 +25,10 @@ import ( "github.com/spf13/cobra" ) -var ( - storageInfoCommandHelpText = `This command fetches information and status of the various aspects -of the openebs storage and its underlying related resources in the openebs namespace. - -Usage: - kubectl openebs describe storage [...names] [flags] - -Flags: - -h, --help help for openebs - --openebs-namespace string to read the openebs namespace from user. - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. -` -) - // NewCmdDescribeStorage displays OpenEBS storage related information. func NewCmdDescribeStorage() *cobra.Command { var casType string + var openebsNs string cmd := &cobra.Command{ Use: "storage", Aliases: []string{"storages", "s"}, @@ -54,7 +40,7 @@ func NewCmdDescribeStorage() *cobra.Command { util.CheckErr(storage.Describe(args, openebsNs, casType), util.Fatal) }, } - cmd.SetUsageTemplate(storageInfoCommandHelpText) - cmd.PersistentFlags().StringVarP(&casType, "cas-type", "", "", "the cas-type filter option for fetching resources") + cmd.PersistentFlags().StringVarP(&openebsNs, "openebs-namespace", "", "", "to read the openebs namespace from user.\nIf not provided it is determined from components.") + cmd.PersistentFlags().StringVarP(&casType, "cas-type", "", "", fmt.Sprintf("the type of the engine %s, %s", util.LVMCasType, util.ZFSCasType)) return cmd } diff --git a/cmd/describe/volume.go b/cmd/describe/volume.go index 4a0e414c..dce278ef 100644 --- a/cmd/describe/volume.go +++ b/cmd/describe/volume.go @@ -22,33 +22,18 @@ import ( "github.com/spf13/cobra" ) -var ( - volumeInfoCommandHelpText = `This command fetches information and status of the various -aspects of a cStor Volume such as ISCSI, Controller, and Replica. - -Usage: - kubectl openebs describe volume [...names] [flags] - -Flags: - -h, --help help for openebs - -n, --namespace string to read the namespace for the pvc. - --openebs-namespace string to read the openebs namespace from user. - If not provided it is determined from components. -` -) - // NewCmdDescribeVolume displays OpenEBS Volume information. func NewCmdDescribeVolume() *cobra.Command { + var openebsNs string cmd := &cobra.Command{ Use: "volume", Aliases: []string{"volumes", "vol", "v"}, Short: "Displays Openebs information", Run: func(cmd *cobra.Command, args []string) { - // TODO: Get this from flags, pflag, etc openebsNS, _ := cmd.Flags().GetString("openebs-namespace") util.CheckErr(volume.Describe(args, openebsNS), util.Fatal) }, } - cmd.SetUsageTemplate(volumeInfoCommandHelpText) + cmd.PersistentFlags().StringVarP(&openebsNs, "openebs-namespace", "", "", "to read the openebs namespace from user.\nIf not provided it is determined from components.") return cmd } diff --git a/cmd/generate/generate.go b/cmd/generate/generate.go deleted file mode 100644 index 3bddad9d..00000000 --- a/cmd/generate/generate.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package generate - -import ( - "strconv" - "strings" - - "github.com/openebs/openebsctl/pkg/generate" - "github.com/openebs/openebsctl/pkg/util" - "github.com/spf13/cobra" -) - -// NewCmdGenerate provides options for generating -func NewCmdGenerate() *cobra.Command { - cmd := &cobra.Command{ - Use: "generate", - Short: "Generate one or more OpenEBS resource like cspc", - ValidArgs: []string{"cspc"}, - } - cmd.AddCommand(NewCmdGenerateCStorStoragePoolCluster()) - return cmd -} - -// NewCmdGenerateCStorStoragePoolCluster provides options for generating cspc -// NOTE: When other custom resources need to be generated, the function -// should be renamed appropriately, as of now it made no sense to generically -// state pools when other pools aren't supported. -func NewCmdGenerateCStorStoragePoolCluster() *cobra.Command { - var nodes, raidType, cap string - var devices int - cmd := &cobra.Command{ - Use: "cspc", - Short: "Generates cspc resources YAML/configuration which can be used to provision cStor storage pool clusters", - Run: func(cmd *cobra.Command, args []string) { - node, _ := cmd.Flags().GetString("nodes") - raid, _ := cmd.Flags().GetString("raidtype") - capacity, _ := cmd.Flags().GetString("capacity") - devs := numDevices(cmd) - nodeList := strings.Split(node, ",") - util.CheckErr(generate.CSPC(nodeList, devs, raid, capacity), util.Fatal) - }, - } - cmd.PersistentFlags().StringVarP(&nodes, "nodes", "", "", - "comma separated set of nodes for pool creation --nodes=node1,node2,node3,node4") - _ = cmd.MarkPersistentFlagRequired("nodes") - cmd.PersistentFlags().StringVarP(&raidType, "raidtype", "", "stripe", - "allowed RAID configuration such as, stripe, mirror, raid, raidz2") - cmd.PersistentFlags().StringVarP(&cap, "capacity", "", "10Gi", - "minimum capacity of the blockdevices to pick up for pool creation") - cmd.PersistentFlags().IntVar(&devices, "number-of-devices", 1, "number of devices per node, selects default based on raid-type") - return cmd -} - -// numDevices figures out the number of devices based on the raid type -func numDevices(cmd *cobra.Command) int { - // if number-of-devices is not set, set it to appropriate value - if !cmd.Flag("number-of-devices").Changed { - var devCount = map[string]int{ - "stripe": 1, - "mirror": 2, - "raidz": 3, - "raidz2": 4} - switch cmd.Flag("raidtype").Value.String() { - case "stripe", "mirror", "raidz", "raidz2": - c := devCount[cmd.Flag("raidtype").Value.String()] - err := cmd.Flags().Set("number-of-devices", strconv.Itoa(c)) - if err != nil { - return 1 - } - return c - } - } else { - d, _ := cmd.Flags().GetInt("number-of-devices") - return d - } - // setting default value to 1 - return 1 -} diff --git a/cmd/get/blockdevice.go b/cmd/get/blockdevice.go deleted file mode 100644 index e7e9799a..00000000 --- a/cmd/get/blockdevice.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package get - -import ( - "github.com/openebs/openebsctl/pkg/blockdevice" - "github.com/openebs/openebsctl/pkg/util" - "github.com/spf13/cobra" -) - -var ( - bdListCommandHelpText = `This command displays status of available OpenEBS BlockDevice(s). - -Usage: - kubectl openebs get bd [flags] - -Flags: - -h, --help help for openebs get bd command - --openebs-namespace string filter by a fixed OpenEBS namespace. -` -) - -// NewCmdGetBD displays status of OpenEBS BlockDevice(s) -func NewCmdGetBD() *cobra.Command { - cmd := &cobra.Command{ - Use: "bd", - Aliases: []string{"bds", "blockdevice", "blockdevices"}, - Short: "Displays status information about BlockDevice(s)", - Run: func(cmd *cobra.Command, args []string) { - // TODO: Should this method create the k8sClient object - openebsNS, _ := cmd.Flags().GetString("openebs-namespace") - util.CheckErr(blockdevice.Get(args, openebsNS), util.Fatal) - }, - } - cmd.SetUsageTemplate(bdListCommandHelpText) - return cmd -} diff --git a/cmd/get/get.go b/cmd/get/get.go index ea25391f..f3e5310f 100644 --- a/cmd/get/get.go +++ b/cmd/get/get.go @@ -20,44 +20,16 @@ import ( "github.com/spf13/cobra" ) -const ( - getCmdHelp = `Display one or many OpenEBS resources like volumes, storages, blockdevices. - -Usage: - kubectl openebs get [volume|storage|bd] [flags] - -Get volumes: - kubectl openebs get volume [flags] - -Get storages: - kubectl openebs get storage [flags] - -Get blockdevices: - kubectl openebs get bd - -Flags: - -h, --help help for openebs get command - --openebs-namespace string filter by a fixed OpenEBS namespace - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. -` -) - // NewCmdGet provides options for managing OpenEBS Volume func NewCmdGet(rootCmd *cobra.Command) *cobra.Command { - var casType string cmd := &cobra.Command{ Use: "get", Short: "Provides fetching operations related to a Volume/Storage", ValidArgs: []string{"storage", "volume", "bd"}, } - cmd.SetUsageTemplate(getCmdHelp) - cmd.PersistentFlags().StringVarP(&casType, "cas-type", "", "", "the cas-type filter option for fetching resources") cmd.AddCommand( NewCmdGetVolume(), NewCmdGetStorage(), - NewCmdGetBD(), ) return cmd } diff --git a/cmd/get/storage.go b/cmd/get/storage.go index 8ca40f72..8547ea99 100644 --- a/cmd/get/storage.go +++ b/cmd/get/storage.go @@ -17,28 +17,17 @@ limitations under the License. package get import ( + "fmt" + "github.com/openebs/openebsctl/pkg/storage" "github.com/openebs/openebsctl/pkg/util" "github.com/spf13/cobra" ) -var ( - storageListCommandHelpText = `This command lists of all/specific known storages in the Cluster. - -Usage: - kubectl openebs get storage [flags] - -Flags: - -h, --help help for openebs get command - --openebs-namespace string filter by a fixed OpenEBS namespace - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. -` -) - // NewCmdGetStorage displays status of OpenEBS Pool(s) func NewCmdGetStorage() *cobra.Command { + var casType string + var openebsNs string cmd := &cobra.Command{ Use: "storage", Aliases: []string{"storages", "s"}, @@ -49,6 +38,7 @@ func NewCmdGetStorage() *cobra.Command { util.CheckErr(storage.Get(args, openebsNS, casType), util.Fatal) }, } - cmd.SetUsageTemplate(storageListCommandHelpText) + cmd.PersistentFlags().StringVarP(&openebsNs, "openebs-namespace", "", "", "to read the openebs namespace from user.\nIf not provided it is determined from components.") + cmd.PersistentFlags().StringVarP(&casType, "cas-type", "", "", fmt.Sprintf("the type of the engine %s, %s", util.LVMCasType, util.ZFSCasType)) return cmd } diff --git a/cmd/get/volume.go b/cmd/get/volume.go index 4e12be09..644fa58e 100644 --- a/cmd/get/volume.go +++ b/cmd/get/volume.go @@ -17,37 +17,28 @@ limitations under the License. package get import ( + "fmt" + "github.com/openebs/openebsctl/pkg/util" "github.com/openebs/openebsctl/pkg/volume" "github.com/spf13/cobra" ) -var ( - volumesListCommandHelpText = `Usage: - kubectl openebs get volume [flags] - -Flags: - -h, --help help for openebs get command - --openebs-namespace string filter by a fixed OpenEBS namespace - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. -` -) - // NewCmdGetVolume displays status of OpenEBS Volume(s) func NewCmdGetVolume() *cobra.Command { + var openebsNs string + var casType string cmd := &cobra.Command{ Use: "volume", Aliases: []string{"vol", "v", "volumes"}, Short: "Displays status information about Volume(s)", Run: func(cmd *cobra.Command, args []string) { - // TODO: Should this method create the k8sClient object openebsNS, _ := cmd.Flags().GetString("openebs-namespace") casType, _ := cmd.Flags().GetString("cas-type") util.CheckErr(volume.Get(args, openebsNS, casType), util.Fatal) }, } - cmd.SetUsageTemplate(volumesListCommandHelpText) + cmd.PersistentFlags().StringVarP(&openebsNs, "openebs-namespace", "", "", "to read the openebs namespace from user.\nIf not provided it is determined from components.") + cmd.PersistentFlags().StringVarP(&casType, "cas-type", "", "", fmt.Sprintf("the type of the engine %s, %s", util.LVMCasType, util.ZFSCasType)) return cmd } diff --git a/cmd/openebs.go b/cmd/openebs.go index 06b07b41..3ae9a436 100644 --- a/cmd/openebs.go +++ b/cmd/openebs.go @@ -22,70 +22,35 @@ import ( "github.com/openebs/openebsctl/cmd/clusterinfo" "github.com/openebs/openebsctl/cmd/completion" "github.com/openebs/openebsctl/cmd/describe" - "github.com/openebs/openebsctl/cmd/generate" "github.com/openebs/openebsctl/cmd/get" - "github.com/openebs/openebsctl/cmd/upgrade" v "github.com/openebs/openebsctl/cmd/version" "github.com/openebs/openebsctl/pkg/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) -const ( - usageTemplate = `Usage: - kubectl openebs [command] [resource] [...names] [flags] - -Available Commands: - completion Outputs shell completion code for the specified shell (bash or zsh) - describe Provide detailed information about an OpenEBS resource - generate Helps generate a storage custom resource - get Provides fetching operations related to a Volume/CSPC - help Help about any command - version Shows openebs kubectl plugin's version - cluster-info Show component version, status and running components for each installed engine - upgrade Upgrade CSI Interfaces and Volumes - -Flags: - -h, --help help for openebs - -n, --namespace string If present, the namespace scope for this CLI request - --openebs-namespace string to read the openebs namespace from user. - If not provided it is determined from components. - --cas-type to specify the cas-type of the engine, for engine based filtering. - ex- cstor, jiva, localpv-lvm, localpv-zfs. - --debug to launch the debugging mode for cstor pvcs. - -c, --kubeconfig Path to configuration file - -Use "kubectl openebs command --help" for more information about a command. -` -) - // Version is the version of the openebsctl binary, info filled by go-releaser var Version = "dev" // NewOpenebsCommand creates the `openebs` command and its nested children. func NewOpenebsCommand() *cobra.Command { - var openebsNs string + //var openebsNs string cmd := &cobra.Command{ Use: "openebs", - ValidArgs: []string{"get", "describe", "completion", "upgrade"}, - Short: "openebs is a a kubectl plugin for interacting with OpenEBS storage components", - Long: `openebs is a a kubectl plugin for interacting with OpenEBS storage components such as storage(pools, volumegroups), volumes, blockdevices, pvcs. + ValidArgs: []string{"get", "describe", "completion"}, + Short: "kubectl openebs is a a kubectl plugin for interacting with OpenEBS storage components", + Long: `openebs is a a kubectl plugin for interacting with OpenEBS storage components such as storage(zfspools, volumegroups), volumes, pvcs. Find out more about OpenEBS on https://openebs.io/`, Version: Version, TraverseChildren: true, } - cmd.SetUsageTemplate(usageTemplate) cmd.AddCommand( - // Add a helper command to show what version of X is installed completion.NewCmdCompletion(cmd), get.NewCmdGet(cmd), describe.NewCmdDescribe(cmd), v.NewCmdVersion(cmd), clusterinfo.NewCmdClusterInfo(cmd), - upgrade.NewCmdVolumeUpgrade(cmd), - generate.NewCmdGenerate(), ) - cmd.PersistentFlags().StringVarP(&openebsNs, "openebs-namespace", "", "", "to read the openebs namespace from user.\nIf not provided it is determined from components.") cmd.PersistentFlags().StringVarP(&util.Kubeconfig, "kubeconfig", "c", "", "path to config file") cmd.Flags().AddGoFlagSet(flag.CommandLine) _ = flag.CommandLine.Parse([]string{}) diff --git a/cmd/upgrade/status.go b/cmd/upgrade/status.go deleted file mode 100644 index ce10bb43..00000000 --- a/cmd/upgrade/status.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package upgrade - -import ( - "github.com/openebs/openebsctl/pkg/upgrade/status" - "github.com/spf13/cobra" -) - -func NewCmdUpgradeStatus() *cobra.Command { - cmd := &cobra.Command{ - Use: "status", - Aliases: []string{"Status"}, - Short: "Display Upgrade-status for a running upgrade-job", - Run: func(cmd *cobra.Command, args []string) { - openebsNS, _ := cmd.Flags().GetString("openebs-namespace") - status.GetJobStatus(openebsNS) - }, - } - return cmd -} diff --git a/cmd/upgrade/upgrade.go b/cmd/upgrade/upgrade.go deleted file mode 100644 index 7d19afee..00000000 --- a/cmd/upgrade/upgrade.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package upgrade - -import ( - "fmt" - - "github.com/openebs/openebsctl/pkg/upgrade" - "github.com/openebs/openebsctl/pkg/util" - "github.com/spf13/cobra" -) - -const ( - upgradeCmdHelp = `Upgrade OpenEBS Data Plane Components - - Usage: - kubectl openebs upgrade volume [flags] - - Flags: - -h, --help help for openebs upgrade command - -f, --file provide menifest file containing job upgrade information - --cas-type [jiva | cStor | LocalPv] specify the cas-type to upgrade - --to-version the desired version for upgradation - --image-prefix if required the image prefix of the volume deployments can be - changed using the flag, defaults to whatever was present on old - deployments. - --image-tag if required the image tags for volume deployments can be changed - to a custom image tag using the flag, - defaults to the --to-version mentioned above. - ` -) - -// NewCmdVolumeUpgrade to upgrade volumes and interfaces -func NewCmdVolumeUpgrade(rootCmd *cobra.Command) *cobra.Command { - upgradeOpts := upgrade.UpgradeOpts{} - cmd := &cobra.Command{ - Use: "upgrade", - Short: "Upgrade Volumes, storage engines, and interfaces in openebs application", - Aliases: []string{"update"}, - Run: func(cmd *cobra.Command, args []string) { - switch upgradeOpts.CasType { - case util.JivaCasType: - upgrade.InstantiateJivaUpgrade(upgradeOpts) - case util.CstorCasType: - upgrade.InstantiateCspcUpgrade(upgradeOpts) - default: - fmt.Println("No or wrong cas-type provided") - fmt.Println("To upgrade other cas-types follow: https://github.com/openebs/upgrade#upgrading-openebs-reources") - } - }, - } - cmd.AddCommand(NewCmdUpgradeStatus()) - cmd.SetUsageTemplate(upgradeCmdHelp) - cmd.PersistentFlags().StringVarP(&upgradeOpts.CasType, "cas-type", "", "", "the cas-type filter option for fetching resources") - cmd.PersistentFlags().StringVarP(&upgradeOpts.ToVersion, "to-version", "", "", "the version to which the resources need to be upgraded") - cmd.PersistentFlags().StringVarP(&upgradeOpts.ImagePrefix, "image-prefix", "", "", "provide image prefix for the volume deployments") - cmd.PersistentFlags().StringVarP(&upgradeOpts.ImageTag, "image-tag", "", "", "provide custom image tag for the volume deployments") - return cmd -} diff --git a/cmd/version/version.go b/cmd/version/version.go index 08bf4f09..a3a5e4e5 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -18,7 +18,7 @@ package get import ( "fmt" - "io/ioutil" + "io" "net/http" "os" @@ -30,14 +30,6 @@ import ( "k8s.io/cli-runtime/pkg/printers" ) -const ( - versionCmdHelp = `Usage: - kubectl openebs version -Flags: - -h, --help help for openebs get command -` -) - // Get versions of components, return "Not Installed" on empty version func getValidVersion(version string) string { if version != "" { @@ -67,25 +59,21 @@ func NewCmdVersion(rootCmd *cobra.Command) *cobra.Command { { Cells: []interface{}{"Client", getValidVersion(rootCmd.Version)}, }, - { - Cells: []interface{}{"OpenEBS CStor", getValidVersion(componentVersionMap[util.CstorCasType])}, - }, - { - Cells: []interface{}{"OpenEBS Jiva", getValidVersion(componentVersionMap[util.JivaCasType])}, - }, { Cells: []interface{}{"OpenEBS LVM LocalPV", getValidVersion(componentVersionMap[util.LVMCasType])}, }, { Cells: []interface{}{"OpenEBS ZFS LocalPV", getValidVersion(componentVersionMap[util.ZFSCasType])}, }, + { + Cells: []interface{}{"OpenEBS HostPath LocalPV", getValidVersion(componentVersionMap[util.LocalPvHostpathCasType])}, + }, } util.TablePrinter(util.VersionColumnDefinition, rows, printers.PrintOptions{Wide: true}) checkForLatestVersion(rootCmd.Version) }, } - cmd.SetUsageTemplate(versionCmdHelp) return cmd } @@ -102,7 +90,7 @@ func checkForLatestVersion(currVersion string) { _ = resp.Body.Close() }() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { // The separator for the error print fmt.Println() diff --git a/docs/cstor/README.md b/docs/cstor/README.md deleted file mode 100644 index c5fd7868..00000000 --- a/docs/cstor/README.md +++ /dev/null @@ -1,272 +0,0 @@ -OpenEBS Logo - -# CSTOR Storage Engine Components - -## Table of Contents -* [cStor](#cstor) - * [Get cStor volumes](#get-cstor-volumes) - * [Get cStor pools](#get-cstor-pools) - * [Describe cStor volumes](#describe-cstor-volumes) - * [Describe cStor pool](#describe-cstor-pool) - * [Describe cStor PVCs](#describe-cstor-pvcs) - * [Debugging cStor Volumes](#debugging-cstor-volumes) - * [Generate CSPC](#generate-cspc) - * [Update CSPC Pools](#update-cspc-pools) -* [BlockDevice](#blockdevice) - * [Get BlockDevices by Nodes](#get-blockdevices-by-nodes) - -* #### `cStor` - * #### Get `cStor` volumes - ```bash - $ kubectl openebs get volumes --cas-type=cstor - NAMESPACE NAME STATUS VERSION CAPACITY STORAGE CLASS ATTACHED ACCESS MODE ATTACHED NODE - cstor pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 Healthy 2.9.0 5.0 GiB cstor-csi-sc Bound ReadWriteOnce N/A - cstor pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc Healthy 2.0.0 20 GiB common-storageclass Bound ReadWriteOnce node1-virtual-machine - ``` - Note: For volumes not attached to any application, the `ATTACH NODE` would be shown as `N/A`. - * #### Get `cStor` pools - ```bash - $ kubectl openebs get storage --cas-type=cstor - NAME HOSTNAME FREE CAPACITY READ ONLY PROVISIONED REPLICAS HEALTHY REPLICAS STATUS AGE - cstor-storage-k5c2 node1-virtual-machine 45 GiB 45 GiB false 1 0 ONLINE 10d2h - default-cstor-disk-dcrm node1-virtual-machine 73 GiB 90 GiB false 7 7 ONLINE 27d2h - default-cstor-disk-fp6v node2-virtual-machine 73 GiB 90 GiB false 7 7 ONLINE 27d2h - default-cstor-disk-rhwj node1-virtual-machine 73 GiB 90 GiB false 7 4 OFFLINE 27d2h - ``` - * #### Describe `cStor` volumes - ```bash - $ kubectl openebs describe volume pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 - - pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 Details : - ----------------- - NAME : pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 - ACCESS MODE : ReadWriteOnce - CSI DRIVER : cstor.csi.openebs.io - STORAGE CLASS : cstor-csi - VOLUME PHASE : Released - VERSION : 2.9.0 - CSPC : cstor-storage - SIZE : 5.0 GiB - STATUS : Init - REPLICA COUNT : 1 - - Portal Details : - ------------------ - IQN : iqn.2016-09.com.openebs.cstor:pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 - VOLUME NAME : pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 - TARGET NODE NAME : node1-virtual-machine - PORTAL : 10.106.27.10:3260 - TARGET IP : 10.106.27.10 - - Replica Details : - ----------------- - NAME TOTAL USED STATUS AGE - pvc-193844d7-3bef-45a3-8b7d-ed3991391b45-cstor-storage-k5c2 72 KiB 4.8 MiB Healthy 10d3h - - Cstor Completed Backup Details : - ------------------------------- - NAME BACKUP NAME VOLUME NAME LAST SNAP NAME - backup4-pvc-b026cde1-28d9-40ff-ba95-2f3a6c1d5668 backup4 pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 backup4 - - Cstor Restores Details : - ----------------------- - NAME RESTORE NAME VOLUME NAME RESTORE SOURCE STORAGE CLASS STATUS - backup4-3cc0839b-8428-4361-8b12-eb8509208871 backup4 pvc-193844d7-3bef-45a3-8b7d-ed3991391b45 192.168.1.165:9000 cstor-csi 0 - ``` - * #### Describe `cStor` pool - ```bash - $ kubectl openebs describe storage default-cstor-disk-fp6v --openebs-namespace=openebs - - default-cstor-disk-fp6v Details : - ---------------- - NAME : default-cstor-disk-fp6v - HOSTNAME : node1-virtual-machine - SIZE : 90 GiB - FREE CAPACITY : 73 GiB - READ ONLY STATUS : false - STATUS : ONLINE - RAID TYPE : stripe - - Blockdevice details : - --------------------- - NAME CAPACITY STATE - blockdevice-8a5b69d8a2b23276f8daeac3c8179f9d 100 GiB Active - - Replica Details : - ----------------- - NAME PVC NAME SIZE STATE - pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc-default-cstor-disk-fp6v mongo 992 MiB Healthy - ``` - * #### Describe `cstor` pvcs - Describe any PVC using this command, it will determine the cas engine and show details accordingly. - ```bash - $ kubectl openebs describe pvc mongo - - mongo Details : - ------------------ - NAME : mongo - NAMESPACE : default - CAS TYPE : cstor - BOUND VOLUME : pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc - ATTACHED TO NODE : node1-virtual-machine - POOL : default-cstor-disk - STORAGE CLASS : common-storageclass - SIZE : 20 GiB - USED : 1.1 GiB - PV STATUS : Healthy - - Target Details : - ---------------- - NAMESPACE NAME READY STATUS AGE IP NODE - openebs pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc-target-7487cbc8bc5ttzl 3/3 Running 26d22h 172.17.0.7 node1-virtual-machine - - Replica Details : - ----------------- - NAME TOTAL USED STATUS AGE - pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc-default-cstor-disk-dcrm 992 MiB 1.1 GiB Healthy 26d23h - pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc-default-cstor-disk-fp6v 992 MiB 1.1 GiB Healthy 26d23h - pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc-default-cstor-disk-rhwj 682 MiB 832 MiB Offline 26d23h - - Additional Details from CVC : - ----------------------------- - NAME : pvc-b84f60ae-3f26-4110-a85d-bce7ec00dacc - REPLICA COUNT : 3 - POOL INFO : [default-cstor-disk-dcrm default-cstor-disk-fp6v default-cstor-disk-rhwj] - VERSION : 2.1.0 - UPGRADING : true - ``` - * #### Debugging `cstor` volumes - _NOTE: Currently supported only for cstor_ - ```bash - $ kubectl openebs describe pvc mongo --openebs-namespace=openebs --debug - ``` - ![img.png](img.png) - - * #### Generate CSPC - - _NOTE: supported RAID Types include stripe, mirror, raidz, raidz2_ - - ##### Supported flags - - Flag Name | Purpose | Example Values | Default-Value - --- | --- | --- | --- - **--nodes** | comma separated list of node's hostnames | `node1` `node1,node2,node3` | "" - --raidtype | defaults to _stripe_, supports | `stripe`, `mirror`, `raidz`, `raidz2` | stripe - --capacity | minimum capacity of individual blockdevices in the CSPC | `10Gi`, `10G`, `10GB` | `10Gi` - --number-of-devices | number of blockdevices in each node | numbers above zero | _value depends on the raidType_ - - - ```bash - # stripe pool example - $ kubectl openebs generate cspc --nodes=shubham - apiVersion: cstor.openebs.io/v1 - kind: CStorPoolCluster - metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs - spec: - pools: - - dataRaidGroups: - - blockDevices: - # /var/openebs/sparse/0-ndm-sparse.img 10GB - - blockDeviceName: sparse-6b277da87b7487e501c03ea0001d6d92 - nodeSelector: - kubernetes.io/hostname: shubham - poolConfig: - dataRaidGroupType: stripe - - # raidz pool example - $ kubectl openebs generate cspc --nodes=node1 --raidtype=raidz - apiVersion: cstor.openebs.io/v1 - kind: CStorPoolCluster - metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs - spec: - pools: - - dataRaidGroups: - - blockDevices: - # /dev/nvme2n1 100.0GiB - - blockDeviceName: blockdevice-8a5b69d8a2b23276f8daeac3c8179f9d - # /dev/nvme1n1 100.0GiB - - blockDeviceName: blockdevice-c21bc3b79a98c7e8508f47558cc94f36 - # /dev/nvme10n1 100.0GiB - - blockDeviceName: blockdevice-e5a1c3c1b66c864588a66d0a7ff8ca58 - nodeSelector: - kubernetes.io/hostname: node1 - poolConfig: - dataRaidGroupType: raidz - - # raidz2 failure example - $ kubectl openebs generate cspc --nodes=minikube --raidtype=raidz2 - raidz2 pool requires a minimum of 6 block device per node - ``` - - * #### Update CSPC Pools - - ```bash - $ kubectl openebs upgrade --cas-type cstor - Fetching CSPC control plane and Data Plane Version - Current Version: 2.12.0 - Desired Version: 3.0.0 - Previous job failed. - Reason: BackoffLimitExceeded - Creating a new Job with name: cstor-cspc-upgrade-vfn87 - Creating Dry-run job... - metadata: - creationTimestamp: null - generateName: cstor-cspc-upgrade- - labels: - cas-type: cstor - name: cstor-cspc-upgrade - namespace: openebs - spec: - backoffLimit: 4 - template: - metadata: - creationTimestamp: null - spec: - containers: - - args: - - cstor-cspc - - --from-version=2.12.0 - - --to-version=3.0.0 - - --v=4 - - cstor-storage - env: - - name: OPENEBS_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: openebs/upgrade:3.0.0 - imagePullPolicy: IfNotPresent - name: upgrade-cstor-cspc-go - resources: {} - restartPolicy: OnFailure - serviceAccountName: openebs-maya-operator - status: {} - Continue?: y - Creating a batch job... - Job Created successfully: - ``` - -* #### `BlockDevice` - * #### Get `BlockDevices` by Nodes - ```bash - $ kubectl openebs get bd - NAME PATH SIZE CLAIMSTATE STATUS FSTYPE MOUNTPOINT - minikube-2 - ├─blockdevice-94312c16fb24476c3a155c34f0c211c3 /dev/sdb1 50 GiB Unclaimed Inactive ext4 /var/lib/kubelet/mntpt - └─blockdevice-94312c16fb24476c3a155c34f0c2143c /dev/sdb1 50 GiB Claimed Active - - minikube-1 - ├─blockdevice-94312c16fb24476c3a155c34f0c6153a /dev/sdb1 50 GiB Claimed Inactive zfs_member /var/openebs/zfsvol - ├─blockdevice-8a5b69d8a2b23276f8daeac3c8179f9d /dev/nvme2n1 100 GiB Claimed Active - └─blockdevice-e5a1c3c1b66c864588a66d0a7ff8ca58 /dev/nvme10n1 100 GiB Claimed Active - - minikube-3 - └─blockdevice-94312c16fb24476c3a155c34f0c6199k /dev/sdb1 50 GiB Claimed Active - ``` - diff --git a/docs/cstor/img.png b/docs/cstor/img.png deleted file mode 100644 index 36145487c6c3a1c9d8f47e4da9e3be2ec6d631d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175223 zcmcG#1yEc~w=PUV2q9R2;O_43NpKJD5EzER-8BUFFgOelf(LhZcX!vpf;$XclJ}hR zovQykxBjYoZ&yuC?_GQM?zQ*RYps6z>Cn%LQmAhU-onAbp~^^$tH8m*gP(uJZ(cv2 zIoH>*c>a6k_(?|X&2#a3V-oUwjPE3&<)mtB>f~zV0E7eC*jfXb9RUtNppB!Mth*g45QI)j`Yoya)YIM~V9xCJ=)1UNXz zJ}WHVbqT}4k-^D`e^hgW94@;i1}uCdkTH43c=eXSMK>!)ph`!=EWGBI zq-C52J@*2G1rBjO0d>wV-D#3)g!l!jnC6mIQcUn0e%_CFu;z^DUj63L`VlC>weyi+ z#y#YoNDvV5QT%V|qPV|bhQs(Au8lg*|LaQ6K|Guw%2No{0(Yr-MWm&(0$w}^M)Ipajn4_UO{zb zHcmZ5hCac@zyQ!Okr6CH~CG?sCT5I!mmxJrhglYzjhfT(c%%QLqvhKaDni~wbS6t z`08Y=&H0F)_uaKuy5-{cef5We#Ts5fPm6eP!*o9W4Oi^b8>QN1>?>BnPDkPtmBCh; z$VH*y5Y}t^?j55tvPU+?l|6}I8t0Si%kEYU_-_pn(AqjDAVmwvtntJCOi@^LwCJVI zUTg-Xk zdckEJfid9Po4Y>&N0Hx9egO@j9C?7K;$!!x~o)Hh;~yAIJIV<{Bf|Sl&@%0JON_ z)ROOoxWDgP6oSF%-=pOY^#x+={lE2VnlrO{F23kLye$)4HkF+G`J{VMs*Ty(cj-0e zE8D8>3?qap%P8iPW6On_O79w4~gX z87S6T)LqUM1P9p~Rgek|`=;Pa65M8ibILueQy{S{|zNrY)#WXZOGG*w(iVu5^<*aI1nyH`;Mq zfgYlUhj4X6xI@rS3z8W8v!yBUAda1h!lr9G$Ap^04y@(I@s=pT+4GvF_ugd`5|qn! z9Z0nk*aceb;UDHN2UUo|0dGEF8zk{z=4Hg1Pl(B6{~18iHjcm9jO%BLPm|fANoxoR zUd@$*V6R8+oN}FSr(WK>RtlEWr5$+Y(0ursMvHoY4Y;dqoF4_4Bx5bnBO)HkQ1IiI z)pn*+fx&*>g7filABIjOZzAgqgsAzE1|B*^zq&?ocSPl~HcRELi0xwBAXob@oZID< z2u=K8!jC%FpC1OJ=bm=wHbcVXb3cr~?4tP?n$P%wov$L=8wq_ZH|F?~eTa$nep@Bk z21|2%Jx5fl`2u<83dvaK(og<=7vaJB7S+-PR?)SAm&<^8LgoL`$}H=zZ&>1-9hG99 z4dQ9Rx^aLhO1`Qq^MT2D30AMSS6%jeDeFAS#s7ZEKuk<7LN0Ru6YdI)Pk`@BM!f-i z5QUI{5eDm>Nl6E)YvHiHLr1dP1}8TyiwFq>Qj)%d%ok@pcrzX8whoPFdU8Qf&B*AU z67WwtRknRVhJ%>ex8~tt>m+IhDUg?a`Us}<-R?BlxNb{6&K;0GDxUrQAO9Tl^v&ryhDomR>Mh zFZuXWOpYXA&gaW(Vh%&TncCOf5Y{qpO}-;+Miq8gmLA5L2|g1twVZO878dzEDe zl+wm3XN*`H<}ZauGJhi&_`rmZN28>(Q!Rrp90_o4&#^{NLZ5204NvS|JS#+!Iv(w( z$U50RX?K!ZFeIcAic9M>baCG&ccL^7DC-vk%H=33wvsN zEb_UYuq$NK#h###fw#V(Bbu#l^^<_Y-f-+V>sWIEZ+F@zpYH}rpzak}SyeY5%dlH^ zJR!WOcVuX{L9T>$=jr8S(s?bRwLg4)N-IsJ`wW|ptGT>F^u+uA#IAYbM*dhx@UQMk z79Ul7Utds*?j`&AL;6qC00Cw=abhDN44+w#t@L=vEgga#CeyU_;igoTt|>mI~K2=~9kG-7^H}QQgV*h=Q@a z-CH7St9`V?p2NW8A#lc$GMV@_BUWMg9x6;x;_Y_>1N*OpbfEf@HZ@uOb!f>x zupQ~?7vVQc&hL+h2uI`+59#)oA?PgeE9Y{kM4}_tPsv#I zz9I@lfkpzABhq=pS~xe#UG1~#T=f?cYPW+u(cBP60p*KSxlG!9N!YkDL#j!b+rK6% z4bTZU>c)MRynfO|0CxmgIqu)qHQbMT?VUrjZdOt6MLo^Qa8Uo{@OtZ7(Duod)J0SJ*P?J}UYVeD{*;ss^GQ(^umLc)y^=%oHarVr)5K8xJ8&&S% z-Mm0oTpSKAwicja{2N9b47b*XNvS>i4XJu#f^LA<2i#Q(eh<5GbgA9Y3FNFU1)#SvbPp!hA$BB0lq%AP~M+#Ry+VarOBv5I zX#Snhyd zybT@YKfzTgNc=DQp7AHxF!xW^7cwUv`ZwT&{NJx_Sy0s5z@?|BYl}7^mWdQQP0-Ky zjztF(s`rkT3Ph6b^=6|!eUGbIu^@t@xVgLCS1Z`)#5#X!&NPDJiiGBYd}p>l(8hN7 ztZ%j}VXDoN8a^#>dDr0rS{CH0wy(P0aa18(THdVx-VRiXd@Xcg#@fTYEZ2ovz1(1^ zv4F+XD2D@>iQ5BF<&O5mc_)gL!wtQ;2ZXV0CAIsjmFETI#{4`ooZ< zr1P7f${*Cd9)3-df^}PD|_zh!7+bcrq8)L4`4Xfb@5_wKPp2wZw))E=?M`z`? zy?Ys=->;*MrK>5r(ga6ZiZ$u9=p6flQ1m(Dg7y;la2Px zfN>&=x~H*-k{Y*bma2Akdw6pBI?O-R@7?ezmn!QMY1jhp@lROYy<jde;oJvGq@34V zjf6i>hBRnmcY&;U=+V%jid}ie$23rbn>vpYi%_gj=IvqmK+i$>=uoT1X0`xFb8`Vf z(?FNF55VBW+=d30W)G`JSdI*q60PJp7-kM*WAra1jP`&ZM#rdSaX+{cPwF0Z!;2PS zl%J>uvYSef+HQ`YsyYq?@o6jq9q?lArmPc#1ucv05-9+3yc#hpnd+SC*p3I!~wQu5oFNNGp2yLz#-4)f5 zQ14{~=6b5LPCjN)@^I;#)6NcMsMQi>I3g?XD%nngB-9!Lk!Vc5ww}6qMe$p5D+<&S zh!q|y*A_8`*IPWMo1}QE@pk%F7qc{j9&Jb5pL3t3fiqhwz|({OGX3K#gq$bYmiN{R zYLviPQmfEvYH#78U+TSP6S@2>CCp~iWD(q!R|}YJDRUWG&FpVWkG#zd*zy)P<)4z4ZKe-n;oj5Wf;!Gaa4|6Yo0(|A(2;cN?#x%zVWxTpO=|Rqsv~wg z5Y*};!{L1_RJVL{k+%3;zZVd_#snBoKpEBHD_Sto=`X$0nQbeJXma`;q5D?C!Cpde z!BW%drK(G1IG|dXKSvx_vPCZvo|ES6l9%*s+(z^@ie~`Db?()oisPDm4;TONO>pX@ zdmAFtGNzu`ouZ~gm^#lJAm2bCV; z9h^hwFR?^}JtrCRA*l98z|_(^BRlRU#{0F^e9F>N4j~Z>IMlOM#lV^)++?#6XbC?n z1qC=8QH<1EX0nC@Jw4RhYUusG*z;b=HFa@VoR8*;>=-ZCoc!1oZl ze$Ru|8ZDbqbpO-ytDagNcoO`IP-7+UzFu$dqTcekbO=By5F|BTIE9WvS^+j@Z5oaR zTDxT)AtnwurPhV5w%mX19ZdgXlR>6)2^l^jMElIM78%c|?f#oRf@U7&V4B|wemFyq z+qWpf@y~Pz=_!V^#?ZU?x@fgcJAt#&Edbe96Gxdpq5SO61hbIv3zK7?Beshuo-Vyq{fHwr2l7zyr{bwB0PEciY zq&-tLZ7{#vc}HdB&F-*P`r-PZccWfFEY?UD7HNjPy`Gm4AOCusQ33HSsnv&BO~SF9 zMa_$Swutr&9^+-WV<(^<{pO}Wx<T#=*E9q;+N|)a(hp+?X-u-yCPMjb3b`B0NiR|k#d@WfSbY6{{^fItEW^VrL3?W}5i|yNLR@iig83^%hJ8zsp>Bx60o#^Z7LRcC zCXZSq(26R{V=*amN+`$&$_4e)>s73S!wRD`7VC^lkr{EfzNmX;)h9WO^ zmf{wknP&8eFHWTk{?@$uZT|w?@w7fyueC8E4o?r^cL))4KUZlo=6?&*EWKTuh>+5$ zXFr*oP0~69>cnvq8Hz^o`!}jHLdGfxTbZuX#ZfL&LZ&`C$0EU{*_-)FJJJN zPOhzRMFt@T?RABxE>RvjtfRyHobU6VJC4wSxmeaS9FTQacn6%_lJ>Dc>_%21im!#b zDS_6}0B;mq5p+QDR6GIWz7<_&Z(3-p7nqn))c6bsmmu2uV^8CXmb>Vb=g!l>Ux)VF z>L!Hw!`iNdJTq6qo+{#rGvE6XwP;YT0^rM!>A^jT^K&KL^7G@bzPTA`TLs!!t8u3f zH6&hoH!lvef<{l*A{G*Z)@7IZVya!F9e3X`5r7=0|X!s)@%iwGK^+b=uiGZ)P$ zeqNY!wWZf;vrJ<5VuXcQy79EJq&sP;o;bFkgD|ya9l`Z6YspO1MnoB_hvPgAH`yCO zZy9DL;`F*xCnv25H@wpErLG9+VACcvq}(w_i{J2N@G$U8?U14RlCcdRPR=)1_nA@O zgp!Oo)w&4YYQdJliWcJ2n$wpw&wk%8@|ci%@!TxU6~BHyie^*b^C8cM7HMbdaOb#Q zjf!UBr#L3{@6)jY@3_WaK)nL#e{1{qt`K(1rO5#a5E+kyJw^kb0{=S3`+5A-(hvXS z_!LhW+K-X{krlt8i}^d<$G{+@|Ib?Lf1UfcwFf9x|D0&=WP>SOwD%ovLv-j8Yf7*K zt#JuTkEq#ptZeXrX^z;Pa9Srk*3ANI^0jl0#P44>8U-IaYlPh#Pf3g?<3rH}J47`p_*@Q| z=#$~Qia^p|tRK7?Hnr!Y37p~GvuEFktYX*`S|>m-9lfv|doHqc3xlT4j@xmQ^R#~j zP=@bmk0OtvkveXmZ7lhCZfLIzV*Sa4Q)+f^XsfO-vZ0#z(88VdW-QVtgwex6v1HZO zmAVNcfVq|>!?8=dI}~0`=$VzxVp-4fbOHHGnJQvW)pWqb=e`-XvItIbM zSkjGqDM(bdn2uY!yj|d4g-$B5x)bmmyD|D-L}@-D0lEcA%jz#;n>g0FWvPe(+f_&r z?(9+E)ZBM!YZe8Sg9h94?1K52LMiw!PM{Gn5$-&YZ{(G3RY{k!#a%MDAbM{BbYP{f zGluF-t(OP|BSD_Ot?5^^&A1d&@fSN(Mi)dqzQ*76h zD^ONlG%JVr1Ghop1)@@ps8>>xAMdBt9nxA2HAVe6OwnK|w3fDLG<`gM8zu!JEPm|9 zDAGQ$7N=Ak`idlP7Ad%xH`gS@BpPQH8C2i8Ft z=ARGokw#ti(>EEe*&Hhpa|_QYUO;UQGyfM6vpu61c`+{%)@hD=PiLoFw(wnXgL$$D<39MX*!5TE=qdgKGR&k zxbW7*_o5hC(v};rIK{DSddQh7Im1cvTa=^MmUe`=7OKga?E%6fT51z|u+L}TiMy== zz&{_(ze0KXZGkb|>1jIJ`XYD8k7NfHf^E8H5OFWsYQprrVo0clSoQoO^I#C%%B^(R zbpvaJM(ry$Elu-T6`HO@Hfo2;?WB%@du)zBoJ%<@R1BJqD{FR^9GL>)@wsL_a^?07%+Cyxr=DHuxH-=Y8|Ih?S6_$B53;g`R8ycarRo_sT{11T@1;{Mc z086?l?|g(9!>gYLziGAX3iyXDJJzii%e=l7#`BenR+<91;xpdny0@ox(T@dPfBN(W z={CjZQG-E|=IYQugXzt>)1FIJiA{Aom~%r*)Do>%4=~5~gZc%x#l#N<^p!^s-yqd^ za>#LTbC=Gsz1P%YB4M2 z!Fxn?&2kBCD!V3v%J}|VW!4JCa)E5hMBR9(*SQmUYRalRA+V@Y6;_f?-u_9B5%a!7 zjLDQF(jg*WU*EwWk}USyw)T8x_(42+m9K;_`zosB{%ed_25ruTWDi%<9YwmKaK!pz z>gjq48A+W#AOA!;GH!hcmivBH-fuz5WYO8=aO)Mawp%=iU@w20{`m<>Mdy)5Au++k zRJR{HZqJ!mJ3E)73kAbEU7ji#J3m;fEw?g573b`v1f&gAX)8y<5?aYNO+e)v?U*FB z=Xj!O{DV*phvGw=@VIK#7irbv~`C>An+>)oWM0<^89uDu1g?V2L} zdM2a#^j$hLSnPun#-O1XL?xmz^YFi7PNBRmbMt&{)X>B70+xJUGkiwPVw3p|{D{3fxWapj(;0s&yt1#aeq? zNmcQ2v{F3WLMu-KNNBXuYj0GO^)ps;=yk76=6YE>SKYkfgT}^|nrBC6eFRpN;-iz9 zZy*Bl$V834Gvt}e7b4PSjn8s-t5rAse5);FBa*ef@{-SbRBRnae8YuvCYVsh9Qvxv zH)(Ih_e?cM$$fg8&6dcfKdz3+RBMTeMk;$O(~XtG>k_E4+RM=_cqF7B`^mSpUxP4p zvgiWGL~0IjjeNE^-tIoM-|f??m4}*a6^K(ezPjome%VyYtSB2wMZuqt zjS^UyAzCuhqGsQZtd`~CC;3h{LIm2f0bkqlz9n)c^ch=Ap$7%VH?M=PQu?}iCU3pu zbEz=b{B&NaeHHb+@nbX}q~JN-bU7(fKM0!CVYSxp%9cT%^Vs9r*s|;pyG-LRbHizs z0L=!ztZP{KXp-GcDc(;-gD)*J4qG^)zL!;FLJ#J7YOkg@LbKm}Qy0$~P53?UcCiRq zd+>R(q7{FKH(SD{<`$M**fYK`tpmBR5kP0|V%IFO>mI?NMf~vv|CbtlFyY#6k~&3! zdy*&L`i`2PY!o!f+v4p#B;9xop4VsES!5p&1QU!j1RK20V);)kK*gIF33=MaBE=F| zYAMTc*5^!Gv)EhV$rkSDh8?5XTco1VE5sB8`^sAXtwF8E0x6Er9yt1$=#FQ4C}iu0 zr4-fmkWchSxIiU` z!>cI(Jt00fa1j_qb2nBwd`StX3=%^UK9Yafjy=@6#fYt~8*{S?x9rW_GRW`48s#ya zDU(6DtK;$~fNu6plrxFwCyOW<3hS6ab--aI__YFtV=o}2KdibZ3Jf(&NTCh7!<~nl zUE&f(MEdpXM{gAs`Di5H#ly{(-u-lUo6Ui2+CA`Z@DCus%gAINMluIv0T7zfN*?F9 zXzXcq0ZQ)(?u8BYw4|jS)O*xYBo7D`H&%P@d+*uVU*Mz)1T@Sl)E>QDO-t*Mfi}4D z?@)`}(zP0TMUR4;+>7l#^svdK&b_-n6^3r#q8&7x|0ry3xhB$_k4yOLZFB zYueBuDNYS&Vvh`c+3j`^I&_PH(NE*4-P}PU<%Rk(Z-lz2iMOxij@g75M2iQ6DwuhP|oml+)7!(-bF<>7mv5=}Rc(P_%6= z$8=#Wmn{r0ecOZ4zU0u*w5MsTovY}`vZ-*212x5!ZM3O+05YJ*=8%|-y|nP^Mx955 zJ2hqG7-nJ|!A>L4g<_*Od82W~Mv@*MX zmY}D%F|1&*Au;)C(LL57T!9Ba`l#6Y5yB!SDx zJS)`HXdIL*yJs68b4TZ346CNl?O-5GOrwtIebmU6nj@;@CqJcWXGxu=^_Fi>Gsv}d z+rd4{*LJ8VMw-)|+FCsN0&z{?T0zXd3uV+0X6nr^bg?vG-8H%vVJBEyE7&xJCp?xtx`Xxs`kHVw;wP8uPyQR}y#f zAjX0~NSp3!%AC6$E?JGff8r9%7!)y{?EEL5Q&1Rd^WgrOo&G8P(*MsE9sb`fXZ-(d zdF#oD0)L>PACPn_*9EhAei47(g|Es84LHVdymJz3v`Xq}+7*YDC*n6DZ^gCB2-op1 z|Ds2?7lhG0J0@fIlzBbSl8DN`_dS$0q)7Kzo48d9+}x}6Og6rK5$W$%^n_T%Ph_oI z^|BbR))duphAGo_n#gQ9rW<8nIHlT%{~aSDRUoXR*FS?QzeKdp33Bb?Wvos3VWPCpD1Q&7~^Z4-J`H`@&F8n zU4V{t9E4tW6?=_;;zWmLnLF7K(rtESQ|DR#B9VT_VRpgGFgt)b&o+!Ppof2CdF8zGrf(^`QL+AZdU*+LD~+ z(^KX*Esw9|(TwagMfI!;xHmk5 zTu6J-^H?$QErWII;YhsQ`QR7TsnFqc@ztM@L|W~tZ-x}phvY3c0U71qdRPa)W$qGD zV}s5gCzu1M-)HM_yKa2NW%%zcyMj34qrsl@888NQdc z;Y%+U{-)BKg%kO0BC(uLgr8%iH)){;8`*LgTFQ>9GFy_>3YF8tyFxY;(mmV%dDB^x zbRv@B-5QRC`?D?Q9W|p+$6`k7$4*QA+7l1$Fvm;od9E}2*FLEol(DU*CO=ZUPqF{L ze!*?GNp6IYc<*h9{*Euv< z7p}ci(WptuNE7E8ohUY6BgB?Y=onnp!2cRM4u6*}!WNu&|0G?xhrGAv?yTxLt5Wc> zsERgywO8ols_omcz4%jI9X>%!AQ2 z>!GsR#0%9NB3om8x|RP{Qq(+SZFx?6&u6Z92F=Y9muSxwjTZaDox1SP5^XJOxHv~( z-8rI`pgD`KJ^|Y=?e)TNN(~NoQ@Y5W$Btdaf1h*r$c7#qIz0NuSXwW1{Yp!h4j#{{KTA%*H$9=69@ z2hG7(3+2Eg92PBW#t(Byid6g?s{D?WHaj!`Gv#4FPJG8b^%c)LemPy2_0XebGs9kC zeFh67FEUWiJZ5KoC5S!nK)KdO|Ci_*+=obyy)dm5QJf7u)j(XDsIEPzNy^1%%)b%| z2Uegom9J-^NQ*5sILXS6ZaF{`>eaSc0Ai`IrELUbhTyNfX~meWm61{W#_@E5zGhuL z!`P^O*3Q*3SCyz5)Bm1M=j$959fmUkohkFPAw@xR`G5o`^qp`x;_j3%{YL(ti{z5ldkYZi49kf+)G=?yjXYI)yj~86O^LX|6>GzK- zp``-GXfC?J>K&aw24t&Jt;xJb*!sfzccr-tj1p=xoS4W~Q;jTz+dL#^xtzb1eB`A5 zFX2tpMo{B6gHmPPs%pgB;z7pF+;>`;Wc@L%XGW!`kU_nvk)s`>XIP2&xq+qSE0*et z;#tJ-@3qiK1P~MkmMZ>qo5p3!x9o+!q4kGE8^iI!PPW&+3n9+%6FCR91Q`$l5Rtu8 zz)ir7Zp}=lm2OX|O&79gsmX0EW76FNQ|=6YS46bOrXk$mIqf3fU&rCF3apFHmK#4) zE83MAB*)rQ-xXZ(aK)OnF<962Cm6wiv-tX3(*rN0mArnX;wDl>^ zDAonguimPE)17Uvt$3FHY35X^;uH`5qlc-ew@-_|y3xpMn3V8=*`xQ@atfILH_Ci| zX?QaM;Fe0Xlsevp92Fb{2K#gu_t56`Bpb)k8A%Trq%zY zE8G=NYD+5Rqkn;qJVQjoA1!=}k;h->j(YAJnq|=D&!>$qCdX2K1!E6@$kOoB2Ms=v z7!98FdtLm-|0M(?Nc)vdxT_nfq!`Jm7$?6l49zG7*q*_^rvdv$KlEQ0PDC4Vq=Hbl z3t^{Sz2c7B6ZBV4_36hxcfnz4hChoFW4mtwh^z2|df9Ku z#1WBZ?YgS>sx+g(y_?w^z7eEaaC|is7J3c zlGtYCh(A|cg}NE5x`~4BL^KLP&cYM6eeVtG0vvBh+8;MOub0dm(5YaSp7d%0w4HW1 z%3W^h^#;OUmyVi9H+mHSi<%5HHnQoZmfK{#POaZ693<3oRs6*tnDa1#T{~@*?(Y!= zn7RIH34!}BrHkZ$)&7svFaNHU|49+^B#ZwiHe%EZKKGC2cYj9K{`nLXS0w{G zrxz@WNAizu=3iHFP=RB5Yb&3ujQ2TdW$$LTKF&rE=0^-XXb$|frf)fh{69JU{wS83 ze{?bw|4pX-FTwWzotlgSmFt-zrIL>PoTInGmgx%GS*}L31Vp~2fzHYWS4dcxQ&@mQ zkjXhUY5U^hBHQ%Xwl1y&yec)-Qwi1Vg6py}Q41y`Kv3)MEfqlh*1-Oyqn0S5jV^p773C0677MUEP`AzG&ZmHy6Uw~vDI)@%kWCr;4^!0uY zn#^#8QrmP&3n(cXtyPQ*#qqLs%b6@a4D=v*oyalv@QklYPVT zeH!XeGRPYo4c0oDKA5uWUndFM!mM3loq+s*bu89Eyu1#GodC%v>D|FsnqRTVR~?7? zkw((s-{vAueb2NEnqXVT?EM6Etoy?*J}LYU^7YRaev)EZl{9!{ldH{N^*=66A~sHg zYMJeis)i}9wQxeWsCgm}yLXA>-Mq)mULxl)>g6*T1d^HB`j91qNetYUPk74-F9{OX zQtdO&F82N01WokIb?1Mnl_m!7U+RpB^u>Y}qSlM+)3mVl=z|+*z^Ph`tWhk61MNM{ z+9z6=t%{Hy-g|%i-PoEdt~{mtiK2P)Pcy`{b){J3wHC0T2Lt6GnQrC(e3zvGoi+g?wn8QwSfKF{UxL!l0d>il<~gX zNTXOlGL~o9-ro9S;cKJky$9EK`#0x$Ar84dy)JrpA_1yTK1zLoJWjukKi%9qQmw@L zJP)#m*?);QBAYw4=Dl2?Nf4__>5;zg(gqJZ&K{$%V96}#iOz@iRO{I0CTF(-U+Wt0 z3nX7^t@*a(Fzn^^Df-8Hn2v-)?cr58tWewg=L>Z1VZ^!#F1D|+ma`S2*08{Ey{=Fp z=Y`2Dhd1ZYAiHt}!IzNI^aT$Vv9fmt11Gu>Ek%kggD@-%jN9mZemfdK3dHoGb9hf^ z4L-pJiSo$hCGdJa&EKJDn1Y{5j3Rk8j~;ayYmN&@H7Is`cq3Up;_t>Vs*=lG?V>h; z=O}d<`hdG+(fTj##8k!b%Tq!8Pknw znW4G?wq-R8JW;ax6%_NHQ~g`t-W-l)nv#v$W0AGf3fiPa+7Jz4P~~EezxTY43pAU^ zqEcG1|GV_g<PQ|GzLjmOKvU~s@4|0^K-Vy@3b8Ic~qzJS4Jr zfP0&eDI4!l_aZ7vn;QJwGvoMwX_$*_Ox7zG!QdP^38rAQz#LsSFgzlloK-&slXw2G zW`POpU(o^!wj>T3af@vw#qS)XBz(t_iBR%E57uH2B8USSmw?q)^0kn4)DjYcIrrNmK$aJuXVG9dnd{Q!5V*Po0 zn^!z{>)N);0;m__w%kbNNGJ7e_;kKYXXmljBREOsN1Mf2g{-6M!80kNVrGL8~|39qO-}=%f z2oKVnTy3OXH(L$1L>uj}V(?gH!3JGwIY%hKj%XMn55SQiy@plPn5QJYH0hp4 z{`47cc(=pJ*7_{ZxUQgb?@h_8m2lXm!4&o_wy==!sl4ZfVH%8wVphET_dzn#BWy~z znA`XOgtEWzP2kZ-4b@Eiz^3$MJJWL^;C|=HarJ2tt>SJI!Agk0vXkc;^mJ$2m}ZaD zkI(^foEwSyZ63{HxO9xOoV%_=FA!9=)1)Ho*>@W>t=Dwbhlcv#Ei(eU7Hu9vA8t9@Yql2%ES z=tvIP0gQD?Fx_o*$y1Z@;t4!ED{gfg-g)&xNOyH!ht89Csd?$^;Csh<#>n`mYw$e( z9{-(>egN)jdQYDNJ&}QMsOxJXDdSUA6m+`*t-CIjJ7M9#EcY{0;-g)gD{pPD{iQxZE@`^%m2d?1u~*y9gwokS zz${&iQ$+kE4cksPpR7&Fr?Kiskf=ya-Sj~`QAGT7 zU{>%uj~9vSsdTgSr2R&XbwW05nZxKtwIKSyFx?1%As&<=&<|OByEPRRr#~lxy(j5S zdS{nC7`Wpjw$@46A-_!I9%+LxAe6 zl$Uw>=?NQ&d9+WVd_qMy;E{v!ASXQbNO^NOV}e&8)kmLQRAJw|p~}YCLi3taMExLT zC(VF|^vPu=8;@{bpmxmZ{DQ|@Lp-NYuss0fgqI|GlpQpQlM|3??P$PFOsL7((lV_u zIZ~Q>E6AEx$T&=0`Qv`L2b!Ne(bM^?VqjjbsLss@4&D>EzL?It-$@27a}(25dqS`eCC5rV{{vheNyH+h0Z=xsy3#So?aodF*l&|8Xkq$X;AQ( z{5ZWfGh11Xu^;qTUBHXBc-CW_n#+NQ_EtqWZLhqhs;sm+Z*JAtxe04{ScevE#XLun z>-B{rq}BjC^;ePd&Z45KjX-oxA6S9Y{S!GSe_tmT9!#_0cTMv6rLg>cbd)IyZu4}6 zwd$Z#YWb)Yf%d3d)l(l)Yt7lty#!Qyc2ygE4b-|{7U&J;m z$?R3{zB1ZbmF#fhQGW97+;lNFS22>IXR+~D0pSJ2-Stu#E0KI=IeApZ(vUq~cp(*k zR=~FSm>vmLH%zHu9erndSmRV)NlZ$(bau(HvlbVXJHF{ZTx2+tT0Sf+-^uQ~ z{Ep(D1ILh+vBF!Scw{Ow8*ZhgLZZ_Yau&10nc)W`G9SN9HBTAVu9a*QNg0T}^FSjWj{qR;_7N4%kR z?Y+ktq_i(rn_r?>!elhrmb!-A`-!etK`uM4oQ>Y8$yl7V$%LyCBdLxG?27`d^)i9a zG^3trl&1UGp@Sp)V%k}<{z~ys0qa%j)&3){|garKG5zDD}sK+_O zOxo|honzT@dVS^(+jgZOr-;}(1C~fE2nr{)Gs=#+P6cx@Lc>Buq;}a62Xejn4St{= z2flhIN5yF98xd_Y(P6duInc*2;N-%OUugMBDn-FiT4}V<@=#xHeymh03Urh`VV26`Nj0Y-!E z9Em`8^ARtG61R9iTW`tK%hC<&OXo!0knK6c*7DLdM=yb2{O$my)B6qjrfGLv+!k2S z7gofx*EM+(e(F{a_M&3H?z3%Nzo8zW$F|PH5#*3?@wXmv3$!+&1$!*--Aeijh1Y+# zd!&m24lY$)rG`0nRYE_f)29x_Op~4)+3_0hl!8oPO&{zB;+%Injt}N=us;;P{7%%6 zmX;XyxjDtNfo$DN^pJYSBYJajrG}{K|VZSF9hgub29Y zC9SXSWBXQNRYGF(@3IFG)7baM9goJ3;8pwlr8LP|gSU*{h=R1V6UWmrPmgj{Vh9Xz z%~zj*q9O^vYY)F!xxt%Tpy1)$Xh5oXf6dTSx$BCuI*~ryldwl`5CS2EbZnH0c=yXN zOi$zE<}MPftWW|##JE;gIkhr?QaP(!91z-r$w+6RcF6pdPDT8k+t4+l#D}e6&Mi56 zUucu#m+PZ6!RwK<<#`TDLRNgP9<_?%Tv3Yk0)CwN@~%9!t^vJP>5rp)Jk5+^zHC{Y z`Pxl}y1|0IGiTfE+G63KjeYvJDz2>6iK`=&YC&f`;M&tZQS^bmakFT7`J4@wJy4{5 z;sN>#yJfup)B?y=kE-3+0*w4vET;MkzOsM+xW6SS?wcuf8;z?nUx<<%ECPM}zLU>f zs&Y_#1`eaXjI+;Kx;Ok>(ts^Mk)F3IzhzL1w#%oV9j2?8>U*c`Y2&RMKOo2RMY|Ct z`{CmqP#7h$nw~K&P8qenW#QWyY4Oj9STy{59IVDtwdR78l+Q!?;@>YX$TiV@`oZ27 zL_u8H3?C+vS?}f8#_9M(+RHF?GVlB`e`(}K)Bkk%Y zAGljTI1ZPpha_qU!`_DMCaHGM5V^_UCN|O2<{(lsOjYH4s-V8x1(;ih7h7X+FH9AN z(UM0#yf|@HTz6~hJ$zzLQ?!oU8uZO&FS&jNT?LV>6RJ*uk+{Z4NF3K*|v7%45?K zhmlB@-@wD!M{Q>ul68YH1vM`-Js!Q&zv70eER}Hw*ljd3j%Vryi`UYMP zdNeh9ykWWJ;BaP*xEsNt84{5%{z$kR_eAg`z?aH;hKTh;&L{PX6`8{H z!Nvs}8VK)9R-WJoc)Od--{orXC!XXgR52Z_>lkY;bzj65P7!GeO6A6f#kmLfc~C#m zH;ud}kv_`!8ov3eVtW3IURsdw4kys;Xfh@o#Ge@7KH2YA=XLV%-O3q9t#MGlP5GBJ zcI4_~9Gd@Xmg|_&eqg|Ot{7Fk>4y0QhvsXbZrsJ6bzP}9*rj))nT6qK+WFI8 zquvr%^r@(Z*=y1TlyH7TE!Jvb2CwdYCB5UaSSC6zZk%%la%B;%okcH()Vu}Aem?p@ z&I0bkvm>5sXhTpwg`{K4M_0Qi0+*qM=$ zv1@0o^{sDZhUtFRjYEIQ#dj+#lb%|sF8T^S(Tnn56p9o!=q)J%8FeKlFyM;{F0ye5 zfS^g#8sHY#1#uZ&*)L&r6h*h#KU7WUGYdRS<|4>7Sm?Gkgzl@fht6<3r#9;|HyRqU zesFNPeX)LcO+J0ISLIpBcsVqjd7J)aZwipW2z4>7MQ0pD3T=dvZJ?xtbpWe%#XtmP z8qOxu*P>1?yo?2V3ER9vsmvjp9x4dvkN;!gHF{o-+jO##nXp$e%-7Eot~U+!EBZ&2 zXE6b^28^gdl7WkHdpQZASgF242*qag{caEH6nn(7{gCnuSutG27%~3PSn&yv4OY>g z?H1E&-D0JISFfg=YmTYLQS(R2jd&c;w{fvP6h2Vl>X{T_pK(eg=Zadg(8N`0ORAC}fvM5d8hHt3L6$u8eCB;uZCZ zPu1m9TpuZ01In>|zdzoIG+L~V^uAH5s(MpNpu!DQOo2&HKpxASl8X&fE=qu7W6K(& zrpPqr*M2e!Ksez{yXr80>K;t)aKGGI!;epWx`zk|avRMF&cxvyjZTk|6+z|F(sBq( z)cA8{MMOr@prOY;aEkF%V^>gE=&ml12}e%idCbC?Ii^c=v}WJ*1N z0uHArn3jF1>K7t-)FfDC<(9aP{%S85xWe^Y!?}F0uk*FM86E*oUyOlxRaDd~b@6aY z2dW&+dgoi>9oZ*DPxrb{AP6__?DbnuQ&g3ist1o)epCeR3Up<#XKnO%(WP{4bzm$X zg?6fCVtC0xqdKd;!_Sf(zGrg8(3cX9RcGO=9Gy>`qg&`|2I6b6?>A4xF{A4D9n-5# z_5jZ0njWDGgrTFD8WEC0dU|=U#L%_~xc%pk<%C#A9$Yd#9B@l)ZF}=jPSaKJA$;t~(0eZN0W)|LnfL>1aarR@|N>v`%=yU=`on&wU8J9%+TLeoR z?|Z9R(JiNHy19$NFTdUWnj1>Eh0Fj%TyEmES2=#hXBZn-icXhAKzbK1ioj7&7!%zC z>VFo}8KO9MOHf4lG;fN1FtFy>7HVcambvQaN?G2$<~DKBd>VYST4NI6e4!v_n?TqF z(BHZc-ah?=C@JMLrzoE@WG=0&Sej38J^-_0!cWZjgEb>$6ktJX`A)WHmlj95J5{D| z_%s&g_I7(@A@UgHcxLM{un@cvaei^#j;!R=*Fj*h(GmWvQlLiU;Mx7(5DhIW2S^krsq)tSJ|Qm;09_mXFC?7_l2#_JOFdBwY2 zTO^{j1vy|a`w!5S8*bF45|!V%dI3v>+!t1Yk8+H&pR{M7i7s|ueeGS4TH}Au`fGda zD-Q4=Fk>bvw;M!Ug95vF zG6T47`_`%?-FACBIA_BqBTXdRVebvN&F*KHPjicHj z$O}=z+)=MNI=A6ES#b0r#(easV;ZNzwda}*^~#A{IWAF0O`DF@da zcPR8O)+b+($Sv12YoGoW#YUOT)fd3Q&1Is|(GA>RVes0b*wAE8rvnkX_G#Pf2M1Dh z$))ktt)ik!V#*KZkd(t3zeOdOn#6XGTcElH&wY+CMOZ=0b5^0()m8(6lmK|ISaiJ8 z;d^c>&mcPSde5GqM&+tHJ zVfo@@snbi5gyW(_$Cs}|)VQVAJhyVA4_m1z5YfQFybm)h+A8p2dw6918uj^kj^fPK zb*c|B8c0_To zF}Ef^Lh@rxhm=Ta2ubY?Ooslv{U~-ua@?_;uuvayhO0Q(IB9OKEsI_#rMTxxn1I+WsQR6DhTh8Rh%^=S~^Y7BOD1ZcOpw zOm~%gZQS94#vHbU-=kPj1&8b=?|6->nK=_wM!s9}<0P_kTcNgcU7+cej0(CV!!n?z z44X<-daTtsqyPX z^NZ)KB)vC>kZ>YA$wEsLp5aL86ymA#p}nCIN97GZa_XG$;W8vWZ{II9z8o|=5^HL! zr>GrKq!@D^Bu{mgA2<_{y?|LAMoSLS@(xYjy#;xAqeBX)ib^@b!D&FBV9~)_Z&_uY zjolr}XM8QdqoD|(Z>K+QG8DN_9SRo}e0y(R)#2qf+NHE1(*pkaRCAiAIkWimh>G*Y zU+8cWHvF)T*t}Hb!r>w?aha8Z#0{km$#0-G;Kxpm>S`a~(lT3^*Cuhh>fzbo&Rp1Z zuH_e&?Y%u3j7D6-&QMV@qbE3g!CuIkU%d9)=Vy579hl*fS#O~B@-va0z1kRpu}vw~ z1=}AJ7k5Iks?NY=+EBJPRf%uC zcNEx!j1q&oRO$bG7Y$-4oh+9Ggt%#1z@sl~a0Xz#@f54xpOeh!NP$ zbL0vTI{$SO9MMRXvm_|WN#2T8ISa#0E>3CEvC{*}lB!JRYV(roxf*>GP3sRi^q%KF z=X%Du_0CW8v9>&iM_1|tcKjc7&IBaN-7gTh0aWmMi!7RZMcMJ*^+iiAuiPq()B~CP z5|G0~qWu!zQj*QA%0hRB{CaZr?RmBj_VBRQJ%ntMruC-Z+5+u=a>%8>I^`a%^=mY< zvO=j?d&46w2#j$j7Y73Do`{o1V=eC<6}RQl$R5?FU2zWbNCj4IIoIF(AL$ zBQ?DQH&gHY0V8BEMGzCybspnV>r&i&qOO$AG>00Km;eB)V(Ita0o^?M??frz@090c zhLD*P(sb3ebW_SY5VO&Rrh(q>{vHK05M|R3< zIzxVvljdS!hNfj*P}))B%PHRz0{6n-cET*#;7f{V2}YGs4%gE{p4-A&NR(vBJ}3O( ze_8ie0}Fg-29}>7bVoqlG=qeeHZ3Fk{%!PT3A=_>WdD62kMPR#{e4izt7sr2{cY6q zyAqWA{gCYc;27V0zg}|@j6;@5qY+S@cN0Do)VqyUt^0;UBChnz4jOS+{h`uJdG=Ln z%*b%?E;`ct>ZWCgA3wMEI4}JnI-Y6*Ocbw)9g2o5!=~&NsW8zvLug@C6+WjYbDjQ;|hNdE>Bi1b;j=13KmRU z)U~v+7N*M?@{fWtR2e_^j=t35J^8XqCj_1n4h|R6rIhX{nJ!I{Q2O^l7qN!< zG%7Q#twP-k>!U_v@844IsL~AM?*R7Y)m44mN4kP{Nkx*v_k2zzSr^EX-4YbtK6e>t zYi`d?*!ibvEZXr}2aejAW6r}2*qhGKZ_Br|$4;-7ok{HhO0th_)1%LX&u=I}PA*aU zM&szrw)mOKW;mMM6EXb`5pUAa;X=4wygClf>G%XFYZN@2aSqo=DvlmUyViI`;)AiJ zj#R#bs_5^;0~BO0&~o-pRysjmI+=V5D0B_N%qq@f`@7Sg!{yCU!`z_20&8nMmq{>9 z77sCNTduI166JavZa>+}HOo8bB6dt^cY_S?+!#GK zHlsjtZVwoHr`4{tWC|pe>9$;AH)^Db^R8Dz9<~SYAdr45f5~i20b?e{-K~>@4l!Me zRxi*@m6Uu}b1lujlF6oG9x(I`R+Qr=Q+TYx5BR}P6p9<{3c=Aur{^8(!gf5p6MXzd zP_^%4OC1(#c6$onip&ZlGs9%wXL)Z9WXVQKr=g_Sd5s*?f7!SM5Wo-*&sP~{2|!I#A1KX^Qi1ly+d#U_JC4%zk#Us zL_e*k9g+d@c%Ea~e2bkcq;;-yxJ#fC}g-@#>|qsA*2>>fL^THs;I_vG9c^Mg<=i(p~FhH4#^Ykb!Ha?^^N+-|O$n&nx z?stK-HRN-Le_Z%lvvZ|QmsgTu^7C~Lnu2jubU@lt9!lvNGhN_{bhJZ z9Vl$ejiz^bU8nwRUhFIK*TuQN$p3}9BZ2d ztW%^JeXM5u$ilp1$qa;+8k>bVhdY!MUCg5h{zmB9w-!T+f!jpE?^^s}NI)&r5H8if z^~gX_v&EN^CiK2(4pmk+z|YewsMx;5A0g39G$Sat1IVR>sI(><$nMi~LGojueWS8| zji*E;Jka8^nGj}8XF&c2Uzj^`sjDZygPk*>kf%+$5yCsyf-A@CoVesQ%5P&^+nn8y zhlC_fau{l`g71Xo!k?ilU}^@u<~t0e4WiRECk~QMlZnGP2$=(4YtNCmdr4byMDMhP zg`n!n5~WLB`ySJ z0(LD_>%jdh6_Pm{zbO}j42af6EETAYyObDGTcq`;bMg!tVfb+Io{_+#I025o7%0pX zluds;aFA9h8%_lcvsz#euAYuu-!GQctg}bb;d%)jZP>ELip{lKIXo#RN$Yd}Dh>yW zlJ)|O!MVp7bVdbMjF`}fZ0m?(ut;O?=L+d0N|}ert#CzF=yV5TPXcXL;l7vUh0DpJ zjgK|jZzV~zA8yCHuUX!ocZ8Y7O@BS$W(?ir%8Vp*&O2TF?R6R1y)lv;@pww4QdQWY zcI>wF;GU&DKd-r36Y$j6dgXAZT?oSbj9j9;?h`!dWPcC}w_%jsiQcmuaP88pLY++% zlSSGwE)$tmZCTBrQiM#sx9{nv`y}ofRP78{T>Duy>^t349wE_}T}#>Xy0P2AlJWDK zC~Mzw+7?GM`tbG_$dsD^-A(F(5>!bx`yhjpU!Q+z2Uq7Pxa8TfNs4au7P5KJJE4&@ zc7eAf!L`g#z|UwDx{vsm;?CjMm*H~(ZISNZOR*YrM$F?9#b zabshwicqPzx&JK5WSZ>%9R8sl{onD=pUB5$d}wPA_qPdJl?cyiV7s+^5IA`j!-cdx zfAn+mj00X7RI)kxP``N z^t80T*4BCG@Dmn&y@aM}WdP9p?<#u8NDf6fFeOYn( z`H}kW0>-u6Y+0Z5)1>F7mW+bnIDbpXTt9GI+mKqXN6_<*oHXhunJzziVZ~xoi9iF1 z+v&lX6_nLn3Pi786JeA~Fzdr>zK*h=`F>*$L70zUxA06ol(E!M{mK-$nFwGWdEPAY z$)yOWw=rA@6G$I{#pea@ZxMJ&JrbRa#;B52HkJKPiOs9X0d<$yRz#o2T#m&AzILTS z`iDBgUB|n&|f%*NRl_2fuNpR;=m$r@ETSXjWOTR9=h>zqi>#0dg;sB z((uIS!Ph9OfX_axgV!=n(K`&G8D$_@u6N{=f8iXC2cExh&I~od-umxQE`iSwPJ@x*>2l@o@Al%Ev_6?b8DCi(X<( z#5%-Pn_UE@lON59D=;&%Z=3yW4z4~nmR25-UilZ8!JU8v;g3hpF z*T30>-)wDv^ty%iDs5p`%?#a*dxc+o*>}$%m1}%t3rUmPHOK!XsvOC&I$zUOUNss^OE9PL`VJ|B)DNlh48)7z<Qii3)s7I6> zhpw=R*(?7lf@$ULdwiF%mg z%5`j@#E00foSo;LafnJl;g0dB1_U&c(Ev$mr8@pYh{If#+04MA52v{ z*A1vkA062`-s9EecA?3vnD8@mZh>ZfL%t__xtVe#Db7J2Z4$cXz`hd560S(o?{q|M ztBf#;|DyS3i#e^VD+xKmmDVqWfc~ZOiw0^l9!2KtM`OYo=F8ghKRQ@m1`M)dBa6je zK?y@Ej)9myq|q48zthMQOjha;agO276p0bhI}`h;3lZhjz)HZMEC$3dBv(bcDZR?T zd4;iR#AF$Y87f}vGb35&GkSi1p;m1?Rw-nH7|uYJ3={{WA!M;Hwr57J*xqu3f-{rP zUAlTAV8&_h?l537LLL(x^%Iwo|3q0~Cp!`>Jf2+Zi1_6~%i#SaaO`3l{8j4oSWDv> zPIo4c<;rO@s^x+w_%3h#rpj>dHLvGjCM<~&0XSp8U=m4=V2PY1Z8l;ND{_1 zT1aPTK$}_dlmhn}u?0e|ET){9C<+=#eLa4O9X7O_L}lrFI>^pGZl6$`RgQAlBtTSv zp|AB?KypS;YxszpwM9y&7L8B|p*8#8-m+oVZw`XQIeH+05v0zgx(aJKFaeh8a0M|D?wJ2mTX zj2>I}e0-E!R2D8hZ3hN1a%+Vi@NcyMvsU-)-`J3si;TsTkhDo192|OvetOeWzIHp+ zmsST_YUH7@ar<9Mu@ukS(%fBNTtfJMa&VMr-H!8DxCE42rm%bo=1#z7T+LH1!zc9r z{t15AuO@u>N`6K?`BBs95EupFiZz2n6+6tFpJY?E&=23v6y#{qU9ec7O zF#9z=RPb#s>vg++9R=YNIzoxA*6obZ|x%HZg5q|CVt# z+@aYOc>0q)_xpqi)#$kCj5O4nXRKAjBR*0yA2hITwAWCp?a?&P+oVvE_XF;dY>dP`)N9 z_Xs_NE}F`eujc7l=yd}o(&{DtOYBnRMD-W$oWHZb+fTDSaZtd7-oS(PkBvo8OGF8| z5z+NyjZZH5_E}_`7i$-GxUkD1H#w3D+qDMgw+kyIuE_*RNA=(qI_Gt(+!f5aLno~b zD9WEQ8Onw!zs&x~FN}o}(nhb%l_y3vWwH7pf=H3$+kGskNn_MjN1FK!Gn6$+bbdh_ z4TUiGs@3MdU^JZm6m8qu@XVJRfY(Z{V)fqTf!+aV9Zc#mIh}*68kg@}oOYRZ`I`Ic z<8GDu#Pr-(i;-{b24MbmSgx_+8R87diUVBE8h7Ae*R%@B^^F@Q53Qh81?z4mWnW&Jz!;bZ! zJaaP(-$JSq*-JQEN2JU`@l_Q`bEi3ExObJ{A(JIkgTUZL2T+490}{8G4nj+G@DGXA z>}#XF+xs*07f3Dl^=uh%o->M+o;cM?NK{Zmv zEONuB(V5cdC#f*4dqGjgto zquo6)>aIMOP~D&TQbj=$avR={qKT0+XLm&hu2P9Vu!5a-9rO{v;oHs9cgj>7@+Lb$ z&t&R6k#o@dr!0@B22W2U138owWkNljqM!_gr$;_@C%6>`1@L6{c*JdgMN7u7C2Xb) zQ!oiAtHI_}od|}UH_5@AMBWAz(>rE`qq*!>B=sgPIh#_Y>I;2Gt>Z#dS?FL5q!n6p zAR?4PrMp>Br8gZ{NgOeVgz|<5E3%R5sFm`d&(AQ~+b6)v;0mywjnwOZ@o$kZB?`+6 z4-S1ipyqh@BA_>|K5IvaAba^nTE{ zH3OJTH~N0$lI&1nR-@oI1&NTfV|V{aY$`G2IpF{+IIz7F6LPoDG~0lWhVT0^HvCj* zQ<^Ru91zBr1G&3JT`1L0F{~{X`4BhGh>?z10d?-4AK82@k9&y=qA73^2;(+I>gssloYItZG@ z7qCyQO&l8UQYW^Ym<%#ce$sP_ZIm}S{LyE4I7nSI0I$U zE`W;kBU46j!Z5v5S@TGBLJV%igD{vBiGq#c3Pt+CY?}6YYmQ7jVmPG`V#+q)u`ybp zNlBfji~Cq>K$_ckVYRzaVF3Nskb};P-sPS@6MSL9)Er@AtNPHQ3Bs@&@T*0zKhD$U z5Gx~LdxzI`0vxa@QE=O7wN*89`&$obI26Ya@SQySguI&Y_i@k3nYFtnk)^_Gil(kb z=;;wYiZ4hCQ&-J$S!;h)A1$rPG_$cmh_L9uuNl^>*1ubAV~t588Fyunv9kbCXka6I z=T~*U2HyRsgF{I@)A{nhGnF5-e8N!?T=aAa(J@zbnlz(JniwA_-5d6Jp9d#&6X!cJ zewPX|3OuOy$ih(Wc?MGtID0r~V0?y%yZIgI{%|Z%H^BENn}*z6^AY7Yk~KuyXjfUu z79-jb;UmbtI_BPo4J%-NG;d>b5~Emvb~#=I;rBqHcz+WcYne*Pi)-A#M7U*2P?k{h zV0&}=cw9esZZEnZV6_cUhk8#z77t5-FIe*+^}#;G!(EgeA%6)6x@x`>oLw>t|AUZh zcTLo>%{{+TsHKzZZ; zow97}M2c!iFPcM#&X$DgEE}FxEU1S+L4BM{#I-@`U+4Ps17*CD;onfq#}s~aQ3|rZ z4U<6nXhm6d5qsJJWpvn(@>^;im$O|25g+P4Ang^HmAk*|Hia6ct>Xbi1mhACuyODXKG4l zttPA<8_p)nwOB;GLtO?M?rv|&{9iF8nzbB?1Tnw3xSitZ$lyvRp}<@@osJI-f<2Jf zTaC9lKwb9&0CCyUm(?T`Epw%1*vo4?k6^p4fm^2s5oIz0>UXLALB)ER#U{n>*OwZ} zZ24TjJG5-?tawG6oYfG4k3w7~*HKRKXta2{6R;bQP9#^mv$TtHxnu8i+VYlnB;o3) z&8ErEn62l_I{LHkfwPh~-Zj$q_xDTSUXsT=hzX&`JmfMT@iYIuGDxe~M=hVG6oQZ{ z&*G|;I{;APovZLcX<&wR-u}-(7qjtqE|kSQ)+{rj(``bCyNPw>u4>T~*qg`U3ue$g z&C%PRH?~jqzs+T!=a&ZN)176T#oF;Tp{TN3Z+kBd5P1j=@xz}4gx^?GG`7Qqw`OJWBi@Kk8`=2kt}J*T8X0kCQD zZ{EX`EHtFHhY@Ws8zWOHM)zXTpDDYFERB4nh6f)l=Kj#bV98e|Pa;8_ z^;XWLAZz~$jOIX(DmL~E6H06)GS1YuKjVmKc0>M>^!j~nv7Dce@AHojb{^Xw)yEl- zv58j0Bi5dNYXa=M{Y+4<%e8&7N2J;DB4jft1Bj{T^i|L+&B~~*a|a%>0jKP_mP2>O z7Iy`ogFd$TWdX*E$4FJr`KpF9wv51B}!;(o@yUCm!z9v zmnp705}So?oof(tpes2))L=rAMl-4t_XXt0$jV-p{Ho-Z-BwZBdeB~NK+NlJnFJ~?E%fT*&Prb=6+3XbJC2- zUMFD4sfI+m#Qp=xBaQPqfUhj>bb2C*F61c>fDJWIAsa5#?XwG7_4^YdKdAc~`Jug} zA9gL}mKst>JW)W@`yK{C1NEMBdN;=9k9^>8^XQ`*MUtV1>R->|$a3V|Wpu$+5io@P zVA-a#Z+z>q}~zv4`AUbV2{- zww8C&(5#ClBrjC+5Gdo{0el8Ra?jTqdcxPQy^FcaO@IY=YQYS9;od6?!)>f{QZwY4 zyzHwB)sX(GJZ^Hp{#z(GGIp8ytEbPI0v6{r(xUHpaj}$I0pHqXdb@f8 zA129ewIh~w73JuvPSt?ZDeHj8BXW-W;I=4<+)eBBzJ?b2?6lI#$dBC7whqT3w>3m##v9ELU`L?w3U?q}x40j>0sO*7$eiNctwBh6% zCud_CgN(-&^Ow7EhzXMT!M+Z&&%~iGoX3`4b}s>z{uwRo6(;xymJts3m5-^I2TW`~ z*^?7&P0vw9+WfU^*?dEnvOXZ}2ED`33yBR8Iimdr>ON*X`? z!R!EKtw<9L38}fn7%VdY+n0YY6XBrfD+y~R#Vm7~rFR#kf5h%U_>%{T-Vr>ZlN-!E zcaP@{PpJu|q4W@3yv{p+8i}^}=HAE)yM!M=Y}6$fE{wkHGMr6U7;DM&3J)nWUNaO) z%w0gBd?19&#<{z6TlxL9CjvIl2xD|(xl`#N6)>UIgBZ> zF+ljfBhJg==+S+qaBkgVZLjxLNP7(_}Om zP30;-$3<!RWihmH>}79xnqU6Cy6aS%P#L5gO0!C z*S%{CtZ>z4J0~cOG0kiXkLVfYzYrrU%aSqKRq9-99ylf9NW(S~hd|-{UE@K;`p|er z(ByIosgYx;r$ulj%rz{+OC=wD*(fW2u{e<0gD>!bePwdOBP?ecJ$&QctlH?W^>O`| z7W7A0>5rM-1T{)nTMCYVPuM>WVS(nkgj@n zkFmGSnd7+yh!U6MeDDrxQhfs}bAY$ljG-5I9NHLiw!s%GIgov%)FFW|gzjE~jc$1|%jhrW`q|ea?fN^l>$0lH62yDf_;bt4@ zmrk`{4GjGaxi+-8z9xB~r?jF9dm2L{8sQc$WWzX&c*Dr{^29QmN1|rS?;qGe{=bGs z*MVMV|0sN(R<;-Yb(-7ovP~B@-*d2X{Lkw7zo+tLyxI+eHYzOUAf{{$R4a}dCr9_o znIt1PqU`r?%F7f{k*7vx5{#2HQ|<;agjespU3t{8zP&kAn?Ug@>=Jpg@r2t|gWZ?W*P>Fvgkw%jiK8mq?qT zm7sKRoW_T4z?hQD5kOHh+uBi^Ff_b6{eVH5ns(mY?GF+tYaOFn(cyryh0&2j^+Lbo zWciE4O07LTVYWZMWH9OYQ0!%&O3DgQW(o;jbA0-T=7ag6`4Efj{X_EE9nW6#<<}&N zq1(b(`8UZYqN&OGf;O2gCiQ#iUv#L|E)cUtLw>i?mZ1Pas?E8#j~=t(AAV2dct#V; z9bVQrtC+tSB7_UT`tH+E2q811**C|+gf2{JM^6Y$9)In7pvCg7x$kg>VzJ^Dy&Xd^ z?8P4TWcxVSHzsm9i)8lbM!q)}_xU2*BgsfZzLB`K)mHjHSJv-TI>S?v1=U2DC;vlNqsx ze^H^G-GBFbP%M#s(zZS9WvA+$jZ00-L|Uyss_4J{FZ_{Is$9lyfjl{x8#HO21^+hz)RZG0$Wo$ZzLFp$aDsNp?A z`J?8MgH490qD)ST^f%@7L-zEj&cjs2MwlAMALv)7Q5MUM34yOF)zxpRi8MG78a>d6 zN|1e!_&-`mQ+cF*%kpO!BX&Me;Xb0(IbUL@a|FoO&5h0o$iQV9icVY<<%MTf(y$AS zBIA+Cv@M^a?I{{$WfCj#(V$dm(qcO}@MgSHptcgnKkPxu*m>sTMg8UL9LQwn<1R%i zBR5uCnj;Yo8vp1#{?@=gQJ}rS1&05_r?T?kI5PeJx4upa*4#iag?%};W{s0gfwrt; ztjBY;hv(N0o-1DqAPnfJ3{R`wg8L>Z#~(IZIOE3JiT&6hwxqnO@QRl3 z&<;?JKYntGcsGuUHC^8s5D9kA$K~R@K>~Z`7K}l9@N@T>2Wm7qRZo~83KumF{!f)= z@I$3JIy{9BE5mxC#`)sR5|()=UdUe2x7OTc%p#ZM{1?lC_&du%sp}6yPM|0eZL{@f zMX&p#2+`J++!`t4`_wX`jDN6Tx&Ml|}p2-QD=BtK4k6f$Llnn+Fw+0300FXZcDyYUL>P#`&xyYi&3lW35;{J<%`U z)1-ADGP9OiJULeFy|e0Rpc~w(J)TMH=M;@_{^C7HyInC|x8)%uAPm)&rM>U5Bd)s& z(9&m{GH>?9`Tj2mpYJy|TB^jfcW#dvE!ZRVRYyUr9IQL)lgOujSv5m@TVW^?`NDEwK&0F_Om0D;GGC-8-BOe(TxDxdq}lVZ;buaFrC`E4%DnZ)a{1n0_r#+2y7@T0a^f5QuPZIzKY==BE6b#J5}ABIkN9EcppYP9x)mcx7`%>W)1U8sEJ$6}a} zmA*YzM}Gp!X!7TFqzv4OGHta&S?%|dU0FosV21a>5k^L_Eu%*04uO+T1B{TC z5z}pyDL<>IJYP%oqP-%~c+1dpM;i@$udI)&fBjrn0H3O;Aw-6m3GKp`Sl-(BswOdz z8}CULjN^F9-W?0$(Yc&zlo-{Lc`Vo^D^d(X5!g`exe_8w)_Y;@QDSKEwEGgn>-gJE zhfum|H;#$or3|w2QoVO)lXBWsDvcj#cea7qg*=;b<%{7z73e@!T@OQHEi7$4nc0&y z)vyQya=7ZNj78@R-A}d2(SZ&fBmfHy-RZ7@LgQ=!)clrs1SJw$Pg*DGNByVt2bJN| z&Bzi;i%;bLHwMlOgghHr-+!*)pH;y%*3!b6an&Dp zCKPpW2%YV%1My=Gz&neeA zm2{|hD{GWCsK+aoDVD6vDf@*{(-YTDTtbZ z86MV}x8fG9V8=-W(hV&pmhx(-6g)3PmvhPLI625RHb#~{v*=FJEYwyo>UtNua$!=? z8iVhjDXBE1-oxhdZj@^~w4_*XIIoDN($9OH<8W@X6GSZ>dlQ#mI0?ZRtjS zlDf^7SD_b1j&wOrq57PyinR1asdPBL*?37rRgzezH!n5DoHSGUMdgR;6JKWRs8l2- z!Q{4zouRk_`ZZxyY`N=RdT>`tp&|mlzju-MvI?ghejik7tW6WM+ga48_Ru;jFw zJp+fCIwIn&wy@Z;TypN_?)b}YsJ9b~t#kkqipMjM&3qeE(1cLFlzOL%7u(+{F}AKn?c=yf-{^56ZC z20XLxH%+TJWi-g*b`wj_o?y+S9S-y_xf(8|G55Vz_|b706rW^PilGi)=0dLO>5eU# z4hh02PaPMQE8NlrTQ(OwVa5dGZX|UL-PTl7vZ!(>(#wjtBZu@XDEl#cdC7@c zJVM%0L2To%M!*HX*)hv&9q+nJPvxc?L-A=gO>MNtRtCb4+J6ZAe(1Qtyb#Xl{$uLV zmR~1=%eeE4 zThl?VasDRpNpM$x&ia_z4!^gc{EuS-ez?V`+(P~H%T%}|QQ$u(9SM=d62SG_fznJP zo_6|ACqkx(5_wq0x8fX0b)1C*T>1j5%_oQwFQcaPv5$2??wNLPwBdT2s<1&sO)#Ib3cMteAF=-qF??KhLje z+T2wN#W~^DY|l`R_nP16Y&>{3hFj>gCvvIi-Kiel-Bx)w9+DrjD4gNWn(iD+of;mx z#DL50VUb2g6F##6o#&t}KId1M#d~D_U?KKhwzt0IB-8PyP+fIhA}wd#`Um&V#| z(hrT7G{a<3lAsG?;MD`L>LRF4L+PY*He75o9bb5}*toauF8Xg zw6Qi)ZL*OQ&G$9rD(ZM4{lbf-o=EJ|aqCfhjinJ)jNDHgr; zM1Y}AwGoi1X?|hKe3w%wSQwzcN5hCfvb#{JGP@~vmA-=Kc8;-92y*&N1hn4ky8sR)T zGi%H-W|Lio#E*R)=gfX@Q3O(B=e)}%p`+K~KL(N{4zBN)C2^L12k7z84RGu}^TkzK z@AXOFG{^F!foN?w-P{V4H2ttjfP}gBpKO~b%~6)Gnr7##Kk5+M!>q1 zz|qGmoD%#=YUeQDRM|7dwEAkGw0qoBNb+fQxFz*)^$%u$*L*bIBjXOhmN5ERsY1W+ z^&J1bN@m=4Q(&3h6(v}_V^V}6NS+f&u}PcUlfPjbzr+Fee)~Gv;c*+I;7J^qWqg%E*LL$F4Wmi4cf4p>~fm+;*Q>8s7{pjhbsXZ2#rxAIDlsBBN97^>&_eNe`C8vP$3L&~F>_$Dvg2elu_c;N5dL?IlT>(0vtb z-lA#o-KmO0hW{mwtRP+<{^S`C&QL=?8pOtLNP7{xLSIq@Rj#Gf!p=snB7-|$yw7;2 zVWI9vXyyTrW5Mrmver_{9nv*BQmFrmDyVkqvUEcA=g zp)&@Vjy22v-c~V%JE-)+TtGf?^O?V)&R!AM;`f3Yxi%P_gF>UFa%mgLLV~xq`ss3czSP{O%FfwVY5otlve3Bal5R7r zQt|ajv-=*AR*u36%k<*bZwqpnl}d^gB-r!GRg=X-k{U1Oy4iLxD}^{BR&rb!CuPfr z4oPO@#=jlmamt06E6PfhO3JaYwUiv+txjgoRTMRY)`&`1!QYC6oxEk3O4Wo~Se2AB znbL|g%O;Cxx{8z=8k8E9PD_R7wG!gu%C1cpB*x$N6UD`$V~qC^3+~4+D%F`v@=D#B z$w(JZO1;8An*n{>*d0r4H+53T8mx^_@2vXNOH148>>Pl~FzgX>m=oOR7BxNo@oAP) zcgLX-=Gy6~*%s^IC0SYdp0OHQCNb(dUKL7B-RdKK03jvIIU%V{PX*$_qL%!F$5o2W zP!Wd(jzSX9Qfar43Vl#GTWR4}ml7w5$xJ`rJyA@MGXGJO7@el*PolM^U z#oSv4#o2xBo=FHKA!rD03GUFilLQM6jk~+MySuvuY24l2-QA&aZKQFANB;A^r_P*H zQ#Ca;^Mwy|v3qmhTduu+>)NZbt!AKBvi6mW&04W>&04x<4=MaASmJ3-HIbVYO%F2p*ThOS$6j|CU*^Q;UqX60AO0Yba zR%YoXe6ZrA=N>bK%lA9juQfyIs3n4?BagB@|7Gmo$C z`L(w5^wlcDgd9|twpc;&0_hX;p!a}R6VZ%AO^OMm`$6N>g7+GT29^`EnUBU(rX3}#&oiVf zhSM>X(b1N9?hxGd^;;Mdt|6nn6$&iXiJn)^+c!{0L=f0Z9cym?2Nda*Xwq-k(2k8&7&ilC9~3~Luu&jA#K+mQst)8c;8>1Ml+Etc!{G^cxq(?MlkSX zz)Q_km@Y~pcz7P_vAG_2JEmf?(c~Ov>KyW6-TeE&8j5WD4;&>k-EsQ8 zyM=B_`_`k!ng_<+Yd9eFb$0M>(*)7j$L7z_ z8yj>Hg09>8Z$-ZMb6_jHM}|&u{_MK5w)1r0lms{Pj;UotITJ6=3qBL|PBV6$qEk1T z++n!Fv2I;9S0=?-fn=bOcMY{@25+?c*>T>@J}`f?;irEz6hgZi1s=L~k@rWS2JK3R@xA}OYrT&;SHc&R#s9$sr0AdE{sBf;*Dg{Zy50g+SIHY*f0~*K3Yny-zkm1e zoO)~T^%gG8P>5B=Mk1&7?@7v1H`@^C$5Yosb%zAa3DMRyPBZ40VrMVpaj#dJ*vdHw zD~gN6{m`yA__Gvyl%)R|1foA$SBRCQ*IlZ6zoxN57p=64okNvy*PE0l}tP4pp=jg+_^q)N{1%&fIUN<~q(YmUm z&ANGiDGK`p`$)Pk(h&0z#8@Na#dIk`eBCPLrBY&zuv$K;_Ju{}h9+MFbWZ~-lXgaC znqpy`j)C|aQjDq21vuMN+vCmOaDg=)T~{>Qqrj!hlk+jT_9j&RVPq^d7d*PYU2YL} zDW_*~CJRQf%h{|OcjBhRl<|Uhu#K23q2JkX zz8E+qTf(Ei-MYOqFkf>u5=@LKPBu@8ES3pPf@JeGqQ8hxZYNG0sYx++4QA;eiJ(JI z*96gve%faqI(?OFT+9|lTxHJt>RwDFNL&i&!#V=K*0kZ!Tfi_VYX~x5#~(S>k9@nK zIPZPiVL+4F>TmI48eS`TgNIW``bToTS$CATZP-b=V~#Djh-t? zAQGRJR}Nxp)KvC?XM)j@mFwL7bVv!)7j5$q3%2Zo#Zr??IGN@h=b^MX`i zFpP8+{Ik8=n{(@kt*A3Sk7u3;g|ifsb*N%#F7CCvEN57GCKJ_$I>z{8M8vSFmOe1m z0I_tTjyh@BHw|b}+Sufak4a<9T!BRPM^T-mR}c*?^JX~3Eh$M=E9LH)q8usg`0E?5 z%uMP}iMRmIUMK>@*q3FFnUF1=&m|&Pb{mb^!e}_15UR~$!;)jND}`+B`C!zCSKhyZ z|NM2#Cck#3R|M(tCMjlZbbqNg5I*5TZILDPBF804%>+5tB&Dm>b)`QH!iph)*DR^j zqif->DqGt?nbz3v{yRBoXD)-drXC=nkJTK{n?bw~n}jeYk6pP7qc%0Yzm61ZVPG8M9JRtao-Bs5<^?@I!@ z>7>g9791O8CPZ*2=S-qqvY2IB0@pFGyg`3`v z)n$)d>lmqX7>CN{rY)Ih5HQu^scL{0uHBw;-)@%xJ%}e}nlO0I+=mB&8cuJtgf1Q< zzDiNOUfleZ)w<^*`Q2O$Tt@_)f5sZK>MD1%CLzmS;yzax8}+BhPD8eNGz&l=-w7P# zN^WvvQTh&d3iOBXP58(unw!DLR3gIZMyt2KA44h~={Ge`Efc5(FCNf5gOOj#hj%0A49Wws0crmWo zzXYg1-pJ1KMLDX}fYsNl?%jz!o|*H}LZ+trpAX*lc0{DGm?_<^8jnq<;Bb_d=cvV! zv5zW^j`yRtt-(DuK9HYs*5-=bwX7#-ARrD36N!lY;Dv!i|JM0pbwiT>W!srM4}(rO zlT1od)Q{2gl5OK-+InqlDvOIWy*-VV>^O4bCrvarZ7j(M=d}5P>5|M^uX1R{rh(oU z3_;o;3BRpL885^Yp3dOqjip!DfryeX5S_yE-FN6I449G5wG-kTAR$PVL`}ES2J;f9 z8H2;Qt+*TjUQge^qb=|T*r%}c*c>50T3LT1meDtSoCbgnfG!GFbD`8 z`W<99wGw$slnc!fVZN~|OVtGC;({i?WNKa}Wwwd*t0VoCVe&-oJ$ZNIysS2xVWu68d?TacH=yNe9Vypu$^G?GXf0;% zG7}y**<-G_4&;0%U)VmezNo=dsM_e1-KaZq8C3nQR#q8_8-jYV&av z7}!QylDc^ZWn}G@>?SVHe9af|1J#pNaBoq37ZFjyR=rUAd64_@4++WR*x7}2trOSP z{n~O5fh$Zyk`->j`%2IAK$l-v;fzU#+*}mN?-?40$oRu$RS%1_#-^4F^c#2sXE4B9wB6 z!MpzPpTBiD!{It)>Xp=EH#@F(Gzz}41mD$#F);d&U*Ke(CXS;J^YcUW*UH`rqOPT0 zptq(o(lS+%fyqixJnkv|a;eeI?UR$57?K6Ph%BLX{SZ!9#j811iZHgQKJAVW|5!LC zoU^y7*cp;Lu%5JR$_bsiERpug%4*|-j!Qj1Mnxs{R##{ixK+hOy4`)FZm3tB;UbWtBlp;1!-U#X**^J%y?SCrt*Z<-$B z;n8Vdkq0k7S#gZq!W0|lfXm2p-X-C7uT}{ESb_&p%HnJhu8_l5&?z9`61?7FkAS5D ztor)nz2jM*ceunMKW}3MN;_5G z{esGniHL@ymBU3e1LVbl;3X{RQB)W{18D-@nnoa<61Xdj@S+> z-ux`J<@}526Ar=p%hr<^zvkSflaui>E1e3fj#ZBM`WK_%|18ZCe`};f>yeSu$9Tzg zfH3PGJ+1XI&U8km600S{=X3>z4pk@tHBUb+Cu7|#H?Kh(fbK&5)$qD~FLNmHGBX|{ z^36LpC{&zrdbA~hx{w2OmrAO5G)X@e8LZDX!R-0{wV0jWalH15q*%Ch%0u6GozU_JGeMxhkfh!w@tw{DPn=5xZqqta~wG)gU2HO$2kw2+7VkN z;!Jc04}9#+`RGVEH_3Ug1M#~Xb5=X*Yv%Aq4h+A`{b-*N_2ET)$8E$%kMl1>)Yqa} zO(T}=>-{~e;UoQpY4ITCKe*NHOT7oX2Jbb?#Z!Nz2PbGOQ+YYR{YDmmHDEU!h1eU4 zi{g4sdd@90-gsH(#uHruM?&#@pB+Am zPS5>(%;%K(NI*cjPA9CsTh8@r=vTTqc)cnJ*u5BqSZ|5;IcfgIPrb97(T}6H0Ka>@ zr5i%JYF#am>K0$1MBspbpI-3?p0Zx8NKEbibBSrZR5A(rO;_`~lIJ_aY?U@&(jPq< z9vnyxt*t=I*qMopYa?Bk#P32_$=v-=%uPN((Y!bT91ZwL#AAHTrU>N5yu;zCF`I~y za!3v3HZ77xG-nz9TrueNJt-OF5x;%x9e6SScr%5^8qwKy`&9_jPU?v!tRBIMGn%lU+P3}jn{W_o@BM7<4U2p~*!6RO=w z`6}7c4}Ndx@}B+I<}SiXZOWV&pI+3(o3%PwwH3xpup;%OtB`FgauayX<3nUJbrJyd zhOLDQQZ{CpdcCaRag4v_!-AXi;Qq-3^CL0IqCeg9jTiRyyF(q$E!%)`N`ZPD5FnVV z*ZkY%Hja(lzIuS?EiCQs_2yGjhF!tYZ$K>jsb)KWmXP{6H7xSY>2^siX19q#ZuVGZ zft|TjSlzL{rIOSVD4h9qgxBO+cZ`nu5!TTf)QSmni(&leO$SGu=IkK0U@;VObh_tF zwOz_L)fIS~TYnB}>((6dKMlV;rxr#8=U~!;FRAdKLJCb0&na_WU5z>z>C}?TwP$)p zfe~kpS$!cQ2!yGAA{}ljhb>EF<40RJ#y5C0AtF~SNFJ?8@$+~Ge9+doMEYUR=WMTF(UhMF_$R-ZLgNPK zTR?;|b+2|-N$pkh54`(3u_=AtNUcYZ-hsiZRKwr&^z2n;@U9Y%Nt0{5JQ8Im9OFw1 z#5W#o3Co}rN=*{J3sg4tN^9)(0Qp?QKOXeCPnVa*W>Q8y`S_w28&jbCSGOR2gt@UP z#F#2`1>2~!oE*8);pzLu%7)B4694s`DN^b%_ymv+fGOO#<`rAvylP}s(qJ^r@hvA0 zdOWe@*smVxy10puQD0!o`UEMdWEK=_$(4mR@dkwXWTcpb2fLNI&7Mh?|F`U(Rv>)t zBveE=KL$S?_lS$(N%UiwyEDu)rwnmKj!Be59^=P?hT`$HE?f-EsRUt;gw;f>vYXfT z@WZXchEUi~s3EvD`JU?t?Zb-{Sv#q0p=-P_4&L=lDtvj?6mL)?mjPpWy&|j|g8p&gj z|G*S1DdO9|;%pO}4a=8ZC?c97B{l+DN{4>b!)v9b*3Np~fIFA+z>aZ}up`{WvAy78 zx%P$1;Y(qH_m8uR<}C~FM^{ttslu)7wPd19W+#N56&&Ow%QS}O+gov(j=fyYN4m9Q zXVWzeiwVMuiiRS_hPv~kO&%6K;-BTfJO9@dIUesHiX8v2j6+aB&&5fhswzDDlv-hk zX}YM8Qo%RVl7SeXSnX?bQ}haPALPl$k(s$}ME;WtFuSP_BljmV)uzT#dhB#IPIKeo zuv!yHSkPkFU{m+>!+iKr?+!*+4t?3eMD||xp?fx%zQ3?+)PxGzP#6sC6`u91ufK=* z07V6hC0g;~-RlvlsBG6PO66$H@a4`%n~+z=Kxr(w|2y7_6?}LaO+UMEdR7>~OE9!p zSp|8m*tI0Fo|-alNGMn~^OmJ5WNaIHnd`=EAyy7?Mh9N1{ddwVTtfaolji!@aFpoD zQ%Mf|)uoI^S;SChuh^_5pGxTq6E}bR@0Ppj6!aPHYbR;JJ7%*SIQq5nQ@!|7xxgx? zbU&bI;eWAkWx!n)XN}z-6Pf%F8=I1nBDMn%23|vEVWv<4T5(_@hW*NG*t@xCS|>=w zmTLPUq^5RnWhn`RZKm#mNB+`9KE61|*Tx1a^L+B5SkRS!mWe)bz&8*(f1?j=j-;V#mCmpb&15!h05(H9DlB!Rj&-4s*>zXW`7t#0={% z^u_w6>hbQafJ3dN2r#R~;TlAxgyV2^q5g0FD!du`k*rC9t+Lok(9RT7nBUDF}D zgDkm_4}7>vE^osz=vY#<@UeeXu5((l=w_4GVxb61p%*n{efw7` zo$*Sg(>+ybhZt_13y@ZivP?24OT;5_sV`$m<@{~0XZlf_rFd4{lB8p|2F^qiU>{QKi}&T9sK{-=7)UFRpjBZuIE=tQUZ~7yE8`${|8kG%DJ*DpeW)ID$xD7O($u@=?`t&M@c|jr zaK)7Qk9_9Aabqgws^$^aiXjmfzowO|GrI7m9ioisE>N zV;Cu`7 z29|kt7yZlKp(@X`2TVoOYj@LAc=|q(_ztuQXrdIus@$#a^R_ePE<3_vX~iR!Cc?9o zAM7Cwp&kwnHLC7=2f|Wx)%G^@Yh|*CY=(v_Ep8WSRD(!y;Xb)eYOr01FO~tq1{e7j5ufP9ygahNfPNU1t8^hA3O0!??;<}bLR;%27 zwcNeFXLTrg7>;4a?=WkupK_kz3G@A$3{P=3pMl*?g!IWOvMztCB&5&ed5j@ zV2X{GJN!DFZ$Uof%FLzU2r|2`m0uUH9NNZJ;(cmAmH)y~#S_FINXXDJDI{j`<{Ymk zO+=eN=|<9>mDNP>0qNvpi9$;LA`@s|6`c~tI2_QYlvwn;H$?eF^_3K2kt>F{I-X_2 z&;DtWvgd}+7vCzLnGne?lw|1eqcV{bJuVK=RKz7UWrsfHnxT?Pf)g_~Tbh zSA`sYKuMZl>X*XL;~6`0I^2={GhO<})Alj3U$REl9}(FNYGL^eA5E4k9-Gn9a$uFm z_%ixe(b5_a%UV+o#EGg=>{G6*G0v~om{!8XyRr*lD}$xEIpHUGqkBfY9hmAb9A4HJ z+b&EM+&9MGMgF8wnn9~q^EkQ#LSm_54sSArLU{?NdoB6opJ1Hx1y$FsX~kR|SY`_Y z*f~R-+Q+rdxK|MW9h$i0-2zj#9WHsyfbAhgnEm$Y6j29GsTi3*6kV=NZQ1p*yA*SA zW7@=j2WC8~nj4aMU=XUBALJ;%Y8fR>pH$T=L$!s;{N(m>CNIy6wFH4XmLn%D>8>t& zq@;YimUmMQLG-0d?)>HT!c1<;v<`?f$-fL!wRtXMb@7P9!Zt+Q#@er#X_n&-mQ5!w z=g$@&R~2@lN}Z1;zRFDn$QBqzWz%Zg|1KxbvW#lOOw=kFA?PPYR~OXk(3_Af)|-(RXLW!3l2H=D5*mu0_q@4+(Azv=0TdJ+?QO?zeb z8u*>zY$x`KZ6&m`P@NAYaMYJV&Yvhy>aAj3TVH0o|`MR zelo9piYy+!ws5=7&2cSWJXG_a?d_sG{<_;COnsm0k*Dp1Cs`2R1h6vt5x#Z!9J&2Z zZwqe=;dCsLJwBzum;R?CM~az(&mJJVIlg;z*?F7|Ss`n1U@r})Vp^T)dM9VM5JdOv z9HV5hW93Xty7>pz_dzn(9O2r?XeSI|(sA~I2*)U|N8C|=eA4ou`18Hpuse11?-$Y991 ze-90-zL<))ro`%w6ywcNv5|l$U)#3vVd8m*cRl?2j7ADDr7@>bGnw2ZzcaEaVm(U087SA7dBE%+%?4p-mgZk3rc-!-kN8=vY{--9eAKQrH5W*-LoaY)Q)X8_ zNjg0gtft22kH|&@pgfV-Bc;YVzB~>ujdv9Xz4T6&;4cQ+8<1L=@C36y-(Y*VDi&UT z6-vYHtdio@(tZBIUZ4E?;f?T!m(Mky{;-Q92HoPoJ+^AYH3jN)7pT(Hk;EK_OY5g` z6OCHucH!q+6EjN#zkXdK3&E_I(-}I$XK{}z%+O#F?!fl6u0vC(lbKeq1c%|HP`QqD zGY~sAWk%=x_#1(w%J@Q?DS%22CvbOrALWg~{SJ9QyL7{yodR!CN6#$Ue)H9*yO9#g zU8VFq@U$p>%8nm}$BO~H=Bl$mi8qJ8=+ZWq83!R7bySMbD?ts8&BL30qPnET~5r{6IkXor{ib1*P-N9NBim)>&!pl62Ne;b^Fyn6@pJfVnaW)J)dB)Z*$T0 zwLGSy6Qw8ArO^3Ux;jZWR-v8DuMBt{KEji=x8S~6= z(($-}^JZgsf$Oj56EshMfZ>XXlr0ZK$gP%UT|Vih;GS2#XK@HohpEORTwGk#-eZqi z#r6R+5KMpBn{sgLVRotqu^d{Ape`PCRXS3SYy zbF}9H4khh2x;`SE^I!_2GH`AQ{I&C~zb<9>Mxl)Zn6B-MmfU7dJ>T?RhSy!2f{i~M z(1syyHSorVs^fZtu+hW}0ebpYfz#WB(WIZkklF!>_@0p52uhzSyVmlZpFC1pd!A$-9_d-sm8EUobPlH2GNQu5#|M4iR?W+C^)U zQ}&ncwv-^v@990T!#bKM1et&jF%;USkxxw9mZl~%G-C;c!}~SvC=mh5mJ3ZK)C4S$ zZJC_6-p>wp7M#J>#VQrF1wew66qLrvIfZhwwSnFxcco>{=yd}+t>L?Ga2z1o*3pyY zv6*7W)D89dkB6q>R_8p2v$nhEtFB(9k|du+mzH&3Y#c@Z+BUZkQAR8I4MmDL(kay+ z2a&xnYai`nl;S$p+TKSkKN2NgBoSC^u#Xd|=dJ%1f;`O7T`i|%5>P8l3@=`B{KC;% zHYX|7QD8h-=1Q4kpqUPUK7}=&Z(CUK_G9=v@v1Bx>erb!i>7Z3skEfva!7M- zx*c>U5J=saZo$^(gR{Myi+s=b@sEEb_9pZ!`dsve|L>kj=>Dxj2mex`4?dMP*pcAm zmXXW$*O|D}P20U+eQ0SSp*AROS$sSbp*ft?~Uc$a`!jY{+ICN3R?Yr)@T9SP76wPo3X^SqX;uofGz6yb#bt zZ1bCDXz<6^oVOS8AUg9vGZmyf-RE$cg@Ti!T;QR99eK~?P+@7t-zXOcIL^po&cml> z@gB|XJIhZe_DF`lfrapTm>i|%8#;+pXDuDQ_nVsndO^FxL1*oI4_EDzGlRPhDu4IK zlbh+o(&$Q`H=)5~-m(3w-jmus&9qe+4|Q*h=!XCaz<2H6UIpAtC5%{`DnGRA;jUp@ z*zD;6mM#%XA+P@fZ`!RJi{~z!lJ<00;szh2s6-RwUcZ=HPOXv-%w^fTRa zHG9AP&G9D@kuRt}N&2u-e}LDD7ix?V-B%D8SOEe1M%hee12K$}lI)p6kmJaA<1!8}v?V8@;?X~5HYE_Kw$BfKIn1TX|Y=kfd*7qet(v3D8C2xsyO340E2`}ma zpd*=nf---8alD+~E zF$eUN1TG9p>Ldh1KBc8&R`5zQ%!Pk%k?76R?# zM>8_THAwex7Rl~lwAzb1EwxmVjJuC3hGKP9o<8?nSw+APgKMYyN*3P)ME$(cH#l+f zYRj!&@p~o4I6H})YQw4AFN%6R(eDwc?R1u+)Za^HjQBXzVuSU&R>OUbSfAuvQaET+ z0f9qd3(fu+i!FaZcBvWfX}J4tu4pkpKDy2hN zcB?wwdG$b`ST>+Ij3)O$o={y%h@Zqf$7RRLm-ztWTfMC%E|LG*ALt{ueQIXelo0|6 z5kSzV^;f^dGpfdz0ONlmJs^Q`)&J&tu+`jK{}t&T`J~G!!QE=ydW;8YOG^BnqPtFK zxZ0UoZdZ&d{L;Tshgh)tdUKZ}ZA#5U5MrW+F@{F!3#9F(T3zp9tM<6@OL0GEdA{dU zHIEcWy9Is1UZRKigkj4?UU<%0&7)qFeQWz|1L?oE2RRKr##m3C7ng2QcggN;IIP?! z^(W4Un!$MvtjciRU}f5{Ev*F_1p#$WWsWDsL565?gw^q_&9iOIRYb%3q8aym%-A*# zesL9_T-9TB_8l$}T&c~mc0bC5yp zuR;B7eE-cf;GBt;CI5fNB|$>=|CURFt*!Ccb*FLE?X{%jp-bb|p*-3P60wr&<7Bn< zK&jhRtgidp3${9@l4zz{GQv?_p$KCQZFggn;_oy@c}x|OPe%`{v5B$yidz(DZW*iO zT(1#A6A7}j@?ev~X|}@5wk&1>WX&Mn?eIk2h^gt+1VTi;6$o4qwrKJ;`0m!aFETP< zN#}g;2ND=uhoA~+oX&hasL7OpwGqLs_d`;=zbx%5^cl#IS>lV~XPd6??Z>Mz`aLml zCs!%5wdT+Nh(Qo&Y?3le?X%xZ5@FCyeqQN{x^=3)eLP7~0^fFMhAW$+V2@BwxP6On zMSWo>(z8jdHnu0BNQh>h1yPn=TElVj3tto)Eb)lxZnVEdMI!X*74VeRIn~hGiy6mS zpnTtr|JfKwf?WUl@-WQ+f$4SgueE+U8IY^LT}8jf16TJS%u!uDtGq72F^w(mtg#;) zh_e=?!Y)|HlJ4(?c#oMQsqZAJksQIyo_z}w$K4+9oj@K6wA(jj=GdZA6A$&(* ze7qgJ`aV692pO^{eA%F0&x#IlGF3Ue8a>RnEKrLKbl#A_wR{{NYyh|Cp@2u>-y%~` z@~ybq$GsAjkJs)|fmNk|-%og1Rj8mj$4YsJR@TCXQCs&XBC>4JEoM9qY9b#i$RKjh_D#N3g z>6t>ptrTviq9|VsROaApDmr{#oylJ=-FEj19E*E@YOjpS9TsP#ng_?dHQ5eiy zM`bk`AGWCzQAGJxqiA)i+T`pOXNc7*x+$TJ;lX3crg!v8F4%Cli0ssSHs%trxO3-| zGCQEq=usgLN>sTunC887AG%j`)fr2Me_qn6oyulB^%#;rwkA~@E~ zsHMo{C?^kZIyl|*ZvT7#J@9a=Ww%Hnb z=@UGDrXjJa90^>-iZeHJOKIzy}#HYry<^bzoO=*3&IQ=pyMihGu zVkX168B`Sx^5DarTs0ndxtxUQ8OsCnYxIbtnxWFQ+6YyfRp`uiz}Wp}U|zAKn!0?b zGkKhbu!n@XJij|wAt0qcyZrYRiv0b8s);~e7pF~i%+{6gA@oYBYc}}9Q~XP#P0_|D zQ~6ypp4q);0D4(7u#0SVdqs678Gsp4glPyRsI&8K7hG%l&!!@W)-a%IK^YMvTR^6%IW&E2Z#i7I3|Ih8#o*|vC zBv-awv!MR`bJH@2X3$WXkwt>YbId&xDUn@#AzYS6RZ{!4F>Q3_3Y32gz2wnitD^v=)P zU|T%z^|rqspmYQ)<_V2CXCf#BaQ!*gyO;Im(Iz93Ck;5?JSNplud8ACtycVM!Lf8J ztm23_WK2Ky*7QMV7p`{uB4srU3V;of$t(GUjC8uCRKuo$5%tw3Emm9b+={*wjU#QIq$0G?sza(syRI|dA>`)4h7tRvjQQ{xKv?h4h4Aed zOPK$UOD3Fv#lHW`h>=g5vZ%Y%?al&#sIniPdFH|PCKQkKlKtwV=^SCtL>5f#RPnFj zHec^bK$CFbfNFgZpyY>t)d70E>zz@8s{2!41mS;j0leK`Bpx|C-oT785vNi#=1r<- zs_gugf^D+G-$kIR6Ym!IAk$k>$vjq*MUIy?lhLM1V{|cTCnP`AAMUGJwex2#%pgYh zWojz%+Ok+AKM`QA`p%O6G*bA!*TCG`X7n%cEO&-8tlSMy8@^z$j7V02lz`G|@zFF^ z@5F}LMR94tZU)*Y-SrsxXMT+{)P}(!jlu)+{6l&5vx@9x(*pdhrg-%`ZD-j2_-R7( z$Zc6a#^p<}Ue%bR)M30K^G)c=ko2_Dl?2-OrmB5``+CqRXqTzz$Iny2QJsPC06%Pv z-PB)&^;_7DQ*AxJII)(s#@r)#@CG-cFqD!q^jnhWc=vmi9xks!(a(;L!$iVy8LISfMX^`F^AFw0cvBx9hWI8HKMr7aEGGz=g(B>#6jH@~*dTexCZc>b zGt)WC(7d2fwsKu@&ZC7}*%Tz{>Wu#!PX5a3XM8qesGFlub2bstVFGeq+7qX|R}c&l zQqc_q%z>zY0Na5I+7UHt)a5p=q}5>pn2z!?m<#VwC0^IhtIzW|rs>BV%w}Rg(raE^ zkKR)Sd+I9Yl`r&1>v_6edUMuBYIfrv>}Z~Hyw0`|>B10PiEQq{_U|zvYXtAf?imcG z1?hO+_BWg*S9ANR$lfqj3TDmxvL}6xBj;~NB+D~a9bxJb`yU{em^%5dNOscIf-_Z^ zH=2BgavjYy8F%>jiS-k#Mk9v*X3*=|S_!7>+|8T_I7b@vAC|>Qw1Ya>WJ@5oa0yZ` z<*xAp5oX9+6Pq6ptlT7^ieNcjvRdQ0i4gN00aZ74E2&30pzDaN+& z^|!_u0mI(M{x#e=y)25=_K&p6=^<&1=XqzvN!I*Ss#{?h!;_15VPO-T5+<=S;fX$DOZ`l%vpOUly1oJ6FHyl&*|bDZAY6p4|D39Bs=Ni8;WOWMe7V>MXw^e_gS79EOLlnlh>jjM|rEq zIKw`l@Xi23F*adslZ_3-k`i9_j*kKksIhQuBcg^B?^^8U#IO}=os#5y=p5%`l&X-E z7pJ4iNbzkQ%5~$%&Y&t6%TKA|6>_ZuIwFlkbaB&hT?kB~$;}&(TwY)<<*KOP8x%9= z)Kc<*YO>9@>SE}yqEl4@#P^!YlAeA3bZG~LGI|0fp0KOG=!=4 zm)Zy7f!MfC<{N}&YESr@?f6~XXIUeu>$p<0V(0mh)-%BB+`rnx8^hCLVN%fZ&>{6_Lmgkt34;tXW?;cyJBT}gIREFFWycoI_J z&ZHkJ{k&hsCvttCu}KU(7^3Z)1!RGN02mm5HYZkqpa*2F5(v-un@S2?+V z%{MCLTh`4EYI;lHp0X$E0UPBMCeO5Yu{wsPCN`m^-j}jtN8!ka>hwut#(9L#fZ1s6 zYpOlp<>lgc1cv}7zS6xkbM|fDD#pm7la4IoPP=DT*Ch)6dU;QV-3tz8O!)%!=GR=t zOz;>S#qqHth5H#`Cw>?DE*Z7)>f` zUI2x@Yb@EpfhtRC%iqZz55(Z(dm|96u=mXgqG9tDP$MV4bTu?#SY6N@m_t@@FF&37^A>4ZJN$KI zOlG(dyA@UGx=W_T&*nn(FOs2iC&t*)BK7%HS8Ap_wkz=yoi|T}(-uekYW?O07OoHP z_#DqiIFA_U;+%oB_g#fQB8&K#C=zw0R!urn7mwUYUj{QFQ4fm8l?{;JWWLM{?LDL& zpMTg!`Fl#~SwDWyX2A(bM&I`SAt=H9XL;2n9pKnC_;D<6^^L~cKW2k;erhLu$j~x| z1~dIrvd1CR?Yyw6x$WtQmv@Y%wblJ=?8()8Wj|reBeAfwrSW}`y z-z_qdC~~q=?KCxfjA1F{VT$kmXqzh8Q%4+jU9GoVN8M11loPTRsUK2E$t^CgCNi<` zUKR~4?2=IN2gT^owBp1(%R1}y>zRms(r|)`EKtS9y7wEL1>G2R1ZhT5dcPbK>)TMG zNOg&6$?qOtf4)JB?V=M{(E@voiHDVSZ%o>LEK)#_=przHCil^%+dF&gKqHkmj#^X< zXpiRcbti)4h!ixlRZ^8_h8hoxbzI5HR(CJWjc(xa4B{N;$|8kwyl3QqZ%INpx_4vO zt}%D7ivv*c59ab&bXDxYz8sy*$x~4^C#WUuv0j4{Z#)J~i1}mTv28!u@^$#NiaGOx zMP#P4m6CsoAM<>34GdXO^qb+T7kM9&NE=Fv++a`rN!+YJ>jR3ExH$a6k^${aHi{$u zeRT|SxLJkmO~=@xugcH(pz`VTUTWe4*1C!6kpADicd$Ioqh!2hbf{>lTB)u!iWiYS zW0dzHiSVKqQ)SgE;(dB6K+5xoKnxk=J%-X$814_8Oh z`&X9ep7PXd!x)OW6aN=^j) z+5{GOetj|n9>~9%mvf`_?LE_tU7F~h_=MIfhQfs)I7tQ;&J+YS z0(DSjRaXl8=_=X)Ar%{a%Lvh}?m9;g+tFcsn&CKMJpc+J@hqcH6Jdl4F;+g6X z&JD9T5p&O{IC0_^Qz!^}=aR&HTi+I`i=#FafPln$CW35?zFU2XHiW-jEhS?aZ6}5p z;i0EKNH0voCg>LdTU`J}?R>txo;BJlaTqhnIB|uENGCgB#t%q6T8BGvmf(&qv++J0 zTwLPczrPVcc)zHvp_Nxv7z~fAoh>AXz($Ns<8j7`&$e|R5T>|Kekb5!(#S>|eD|n( zD7ANkBv7K#sgM|0(!4!f!L~|xZ9=-tM5!XG`d}g3v&?l_I|EszherRJ#QtE_*U$_C;BLdSa=+G;v* zgY&le4Zf=fS47`<&O|(B=HH8Iy3HA^$MYR`ug^M2+v8`vgWQ!&aA8PTOeXqPS$1{X zU&B_4M{!s9b=ZO=rITzw7w1$_%Dg+f$GS5XahWL%avqYgINwLL zBo(dG+wrnA;@P8J6F-#551@--oPZMVI>PJa<`-I= z;Hh->4~L)EW70jhc-`d>O?o(g#(y32aX2h#+MQGeWtBN17p`jJwjjf#&scYiMnLMi zwrc=>vqrY-Q4@Pyb-)8#FnU>T-f@WL5jusN<0y#x5(kFoWK7jn3fRE~r3mRni@RUNl|l>Yfmm(OUuQfKaR% z_qmw78WO-{>3A_7W1T0v9^l4wNW7M;`-@PMy}^i)Yt%PsZIV)0$aE#sK<(=QkAp{L z8m@ze9Id+F%q_J*sYVZSD(Fr*it@LCch zOm6%7oX%6dO^iyhArUU6!8DCa5n=jEe1XOsP)3MUgNmX@4pg4~{jfP%0OO*!cMM)) zMIWhLV*u)S>{M+$2ZSZnG!$3|)hI7S^0x2?HpNq>+<$FkOYQ3WOOAue*Pdee=y5iu z`_C@AWI*`6Ek%rQwL=tl%HL0^IFG0ei}p+ESF#yB4}jzpBaQyEGE}dhbw88lEE*~k z5`N$k#grf?JH%pR5j>|zJ(CH-RGLQ*k>tpVY7)d;ek*?RDYMCkNC+Q$p&Xf1kuJYH zb*ozawc+N3TQ=<_e<>wV*XnE8!Uxx3E=k!&1xcBSy8=bdx|+cD>}m5tTJ->>Na} z3`o49D(EEF(P0Vtsc@MDnP-?X8cXceyoI8?INQ)~rr0^Ox;@?;O?C9EtF5$vB)3^1 zW5?;rnNtm9%Y68B>Rq@&mWY_v*9D}A{|9?#8B|Bt?fDQq2@otmu;3otEx5b8ySux) zb8vTecXv3rySqD_;dx%UckaiTs+pRa`{h)h-Md%s>gwIQd;R`vtrVxY>QVR)KX_QT z;n4Vokx1pHs%&q+MBAHR{u@c_9?!(So2~WmcN3LKIdzTpJkQeXYX^Pvb%Pv`D(MzH zPL4gXPj7pS^Ctl7{aqjvQ3m^#Dqd>c@|>Ty2#lXNLV zqDMw$np^&LHNAhXFuN|fg#sYn(ueh`IB@T-k@D;omMR8!1#3)^H?k=h{2&%iub3xSbhPVDqv>8BGGRcYkl-cjk%f@ebVo0>rcFlBq=77n99yK$9LxSaAH?UEH15s~#i zgu&jJG!x6MClbTS0Mvsw-NGYb<4}y}!22@P*4}s%k?tI#`dm?Gxk*$}p*fv(XRYGM za6w`?yIoPvU1}}BBz{O${&NoLL(*+?cMXV+?A3ptIJsUNB>Ga%X=d{NIkDC05b?@* z*og>qDCu*>#whj^ElVmx3a)5~v`-8&A<6Wj)TYL0LT$RhMB~cd;Ro6z8`7#y@Yu_p z={ju^B8gB32GJSjQ)p{#nDgVan6$G|atHln&!ws~rk1ImN_FLV<}Xipuxpz<4zPc& zTfX7GnR5dlfhNyG$);=+gJNP_@--T2>Cu?wb`^{0_vRZ zM8EQK!t64|UfE z*6}Nz8)T#O-ZUr0E2$8#8)K+zlh@W}`^=b}S=wuqX9%xUwBjJk8(`L!>9+LB7vz9-mlXtpDb$wY~4)O@+&+EEz0S7r+*4FWTiW_wwd7A72 znHXsDetW0Y?(zl$hx&$2bNP)Uji+2r>$+r=jF{XjaJRD;v@p&4*RKEQQ)m3!`WF!x z5X~`>*!8ye0o`awZere@rxU+@yx9&JB z4$^ekADFzpO5Iq^E_K!3v~6b$RU~t|=q>-|Y&6^4BMt#~5}bOt#|72lgT7=;hUi~A z;5(ge@}Jt7Xmk>~OEC)xVby}xmma0eZnr-^`F6Pr5s!C-S_KMe+T>q2nkKh!f7IG; z)T6o1KJsbNm3bh4R3F&cLK8L)>gG>#%O-!-d*xnvzu2e5M58ns?AOP>e=-_a=u9oV zy5SzLJ$hZW^Zk7{FsdljZvMQRz1hU=wie5K+r7rGAu$^i>#eiyQeoVYWs-*|xa0-s zaH}yNFu3ox+O&4%!9-p{V~-;W0b~LSKxEfx!Ub9z-%#*4N=D;A4jm%$U zYJYB+*|{-ASBT%8TU)mj8euIqQU)TE+{-nUhpQU7^l$DC@*RJw1-Wi7T7Mb5whGn! zV<+SkXfC2)HqwHDYqi{0AW?dFkZr}8=^Kw7ZX%B*eP?p3$>UVYoqOqKDnbC zxfam~=jV*DrZ2xoqlga2)z^kLpC=~K*U9|ZGAx}R=MLs7a#bA4yh~?Q&81lC6UnR^ zbFwR;aXRi`j&5;A2`)I-;g?@?F#`s^3b)t9IQgx?ydF-)t&qy^Lk@@)R|?Wsx>WSA zom6f-2TckUqV?brKXY5g<>nGZ`ru}R+;YfiJ(35G#6oqs+==arnKRt5^9iU^xxTd7 zJA~o9Byk<2ABaxFoD!5?zx%augNvejmC+K?&h52tBOXL@E9PPy;$uRoAW zjFfk>xRA!^5^2^g8_vR&`3(>B@~=ZoUPm?b;j#?5=yA8aq?R%osHcTW_WQUks&b(j zI1ms-c2=(Sx^#Wv`d0mmZ2wy-H^?7PBZqeB79%_V&Y2S1oErEn$HcBaFb2d%<}A_v zgT{cBJm=@h9ba(q=ptT}*8LPw`)DkTFg;>v6rQP~c{EyzF=YUc7N(4{y4tUYB~_FE zy!GQolhl@d zUoOPW+HamKj!E4bu(Gq1?vvDo~ zC56k=d@J_KV8s^qTp;avfW>Qjx^RxGK}xBa(lleP+2HM>WwCS32WRkQS&L`CzhA7r zlGql+<$SNz9lsw%jCArP^ywwPFJWEl_0MRXIycejPF%NL6R?VF?$(_lA*n-s=axvo zjB6I0TCM#G_aMJi?RI+bmi^+jkyJ!sG`0(y99?tO%_cZFxNlVIt-U|{T>6PoGviNA z+426bNRN5pkvRU{Eb*d~U-0lzpgFAilNe>eG^}^irn-^~B1_Bl>8kP8n$sc9;h{D8 z9z>O^t?x(6IUx{_E}^BhVY8R8(HWz;^o<+Of@~};Ln|le2g_s%4e(beLChVo=o*nk z`1sW|ZE=1q-xj64HP8$sll&edmQqg|$LYGdd6vCA2yQw_0HP07pRQ~lqL+0%_Ijh% zV=k97WNUSdlIt%tEqixoj}h|SoWZZIipYGj(+$R)NC@%V?52MnZEgPI#J7dqUPf|R z>sEON#h0x|5{^`0tmPTZ{3+hsBmRBvIh{9y9>Ss`?ftcylZ&@yKhnVG9T8Ckfa%s5 zFT<$?*mPy=U#ky%oc)@xE+)l|6tY5|t7$=6Z#MrO4Gp0fd77`lyiHly%QeIJLF#%w zd;3UQs(^oQ?dCL%#^&-Tc_-J{yFj;N`D$$C3VSf0H2V=E4(a;&xhzFfs`!ed#I&9M z#Bd=VJksS>xB3fqBElOsq5FG(+c*(+?66M9w9b~u5^Ht^i~l5`)0T+}KB{&|$TFO_XD_ zn&bE3v+^RNbCLt^u(G4wTEw^$a}A21!#~+qNUZFI2@oB$vWchb#BrGH!tEl^xMsYq z;ks?|Q$OYAI-Xm@Z|&oqmB`s-AD}?@?!KtcXgVua^3cJR-?pj<(#- zu{;?Rb+SN7B^IrcsTX5g{N)IuJty~6{5eX_H7hhBKdVxjIcMwZxPp4@kM*UL8RdkO z2<0`+R?;(VR;}k`0SQ4#>FV+du*oE6ASJ2g;p31_{^4*{Z8Wauq_Emu=C%Yem~3s~ zl^^hjxb7<*>6tR7Hn<42Te|S9;{HZOP>OlDE_S$fH(s>XI482;`p*8`5#y*Aa?&T8_5W!k{UkW=&aQSby8P z-0|uMEvFIL@o{m6S=W`Y;MODE2vZbWldB63Y9LBD(x;$as}gS8bJ8iT9Mx=9>h=uN z+VUC+y}1py5hS;TMX;am%y!~NWIhT=n5CtiMmwacxY343R-)LmmP3JetcU{WwSar+ z8^*Uw%(wPqtGecLcw)rqJGC2mGiV5~m~_cJkv&KCwaLpJ1l49tG<}>W#3rX7x|@wq zm%*!W3`Gnz3VPX{%G_ep%;vQQSP2edI;#cEY1Ke&sA!w$bT^z$U?Hr518X8zFl)ch z4eC*=>9>iKKzeHd5h&04@Z*n7`&EFy1!m+|6RejyWb6*w?6axrTRZB3HE=CD19j$Y ziw63KIg-`o`i6m@a1R$G;6JivCFub0=X?6qIFu=i=wZ8Nr)sQ0HWrb#MsHEKPO@!W zM%V+h<(`CbT~Zefdhf`6mR1-%sXS^l#Va8h7wvwzyvv zC`5_KrAC)mRqrSs2K0XF;jF>Xq-d9ilp|_sxq|s zXUGl!2)=ZBO?$FoyYt%j3hi>uLM7Xx-hgkec8v-!FR9WKrqSvn)YDw|+swGQ@nlz` z1k**ZBmH^YHs|jj%;i7UBn7%an_h$-z~vJEESZdZIQR3R5DP6Jw%hLnTBX4hmv|x_ zW(+nU^h{gdQ1JXT`to6eFj+oVH9zf-$wL&~@;0O-_`>DOFM@EE@mrF*90fr}O>*Oi zVqGnXX0cOn%us{XT}NFNbBnQS>_fa4Vu!qqsQEe^jtB%z+WnCZt0(u&lAI?}`@itO z+Dpm)B7T9T@J>A>_|-)7QrUc@qKCY035t^dH^r`Xf_qWsUj!`lW9s#UrFP06lQPP` zx^LIMhpQ_^dLkRm6xG1R+J_yA5zCJpl~Pw#$noNxhdP8guG}ECT9L19yl3RJcNz}= zvZ~|ce1b{fDv+%;$#jxGNd$z+!@sV1KQ~GonJ@zoLK8O?x5WdsOhn6uT{@_f??^L> zp|MB31WD-C%9_TvT658Vto<|#i~HbT*nS=14s7G>!?nh?CdlKZppmLF=hP(m5en0J ze;L&Et_jk>4Xdc#+`~rek}G-*39ua+&01Omz8=N*no<#kPmxs=v|uktr^opzIrqdf%!FsmPRm3F;_s$BO$O~jPx+=pxx-wu7J&>JjHgW6usSnzq zR^mg2G1>2~BWh(yLge+P9Rg~+;vDb-qDQ1`C_SW8luTWbQV#iIf+0#?T#z%)CzBYB zSRcNt%X)6l^aCLz*T;j(#SB^J{AP2x!XI@gL@FBw$)(&d@>aQe_f@p)hUmOQB5LkK z7P8a7WH7)Nef{$%8WU+g7LtB`#>K|0J_ooM_+`$*+b!Oc*H$31=5Z&2@HxV8X8K%y z#3k?7FD*3%SeVL`8?DNeBdpcM%s1$BnxQ>h*NzezP|`x7%Z#r1VokygpN0)YQ(cIJ zkvR;`^d~#%M(nA)RT47B7YR7MvGTI&Auq0rl@J+?o=-4eu791m)7SxC`=mk4q>?tO z5vc>2?LxqO6_oK5HtQRJ1`1|LPgfEEH+^)(MyAM z{Te~l|`U*2VARovP> z5fIc##x3j1@8oCI-dC5+D>bKnODKENW0f01Ovp`snE%Z2|{5c={zl#r6PzKv5hTut!Elt3+9iyzd6E;yLxuhYWxsp_XyWB zF(^8@xGg%QwawGiP?L?q*{OquU-`R8;^CPQ!lCIT4{; zuuVUH8M!A9@n7fX=PF*FZ6Gx^_4biDb%|5#gQ(Q7uQ*^-81<7GI$Gmf z5vNW(4#p&vnwAiAje9!!D22BsR^li!%v}U!E)~NipOB)eO?h!up`Lb5Z)@KhIwc1>>0l zN~BvIS*Is6_GIU&MQG82B`Jk=m{+znIKU%hKFc6()VvtZr`U_IKj~kC9Y^4Zbk(-e z$NcOacP>Jj%}{#dv?<>bdJG=6cW4`5p6fhNZR&y{Ah5=*3)Em|zOo!Y&9qCOH4bZq ze^VI_`XPbdX~{@|CYb|R(GtH{O*cTxv@8yYw<~pS-BF%+ncNJ+(`3ELXz{B1kTN)v z<$kBOVDp<+o?li1BX`svj?_41cTX$Y=+vtNX()a>9*KWb5xZ9B7*&x_d3_}}t3UmR zN`mo0Q0VebGTWx{e(_{E($?|=cl^Lm;5G|u{cszNB$KmopQ%4FG=RWSj&X^&F@e^b z@~Wxjg=*5sY!OM_PA?TXHz|#-74;dc=c0$&FYi$%%(j@Q8yelKpC$3@k6-I{D@Ee> zu>)H60_1#f6mo-YJ#HIw>?>NmhP|!}IrMK(-6(S?Yu-hH^muSMnj^(tQ&{?2bJp&X z@rAoGY9(nn6P2H@QqV4&aqURb!Z$DKFj=`1sFni0=W;)7t)qVWCp-36J-IR&^a4ws zUtZF)N*i0UTgdWh5c(EgRq_!%zXeuzcFF0Xi8($b;A4)l9V?a>eX@-&JfC+zCJ#?A zX2FKI5=k>Z8T*mAcypdDoZOW=k*uKLl19OXh(`}uTtb{?!b=@2o1s!li$`n0eMLMh z?q!QVlczKZ%XGLO)epU?nWd5?(YeGCM{ax(f-)2(3Fd3zcka+%-XS77X!w>@wttL> z1s8NK4_P4;C<2Lf5NcnOBIJmE01}QHcG^uJ6?@l6VjrqwDOQ~hb$0<$s)bA9k zIguAEZ=RSK+#uoUN7&64gmQs@Ov#HsA@!%}+=yH}f4zBD=namvKbc9PrE0nEPSZ{i zHxXXGhne3H>C!j;)Tv(|lPwYu-t6CutKGAn-n{h`7uaz>=<#_(=&$&Ev$^!} zF_31kOmW$uVCD5wzF=D%QeRL4zH_~>2t$e%y4#h83)$bw7*nkd z&?~20a&3mP@;^{TmMYXk?JpsXj^{_(b>kRLwA~9A?ywI*|0x4c1tE!lEe_V%I+s`Q zN*rFqN;oHmJ#bx)18 zie{y@dSGAx1_>!Rs4WTt@X%LES+;adzrgNTpzD)+!w>{dBv*f7{TyVdWgk2^5e_)y zulWQ@dY)-+2S+SbteV@yJ2`)(JC%44YF8b;7Y|jq%a&BYo&~1qJanv}T-^^q{<`8o zvrLgKcg+_XY%CVgU`EEbT>XVnWy+C3rM59XGX{LZMt8}94CB5CDTFgc@8rEJsWJfJ z_T5(|J`Lrk?W{tQ;wUrHzvsJOl|qSmy(RQ9QPJUYhHMO5#cVDvFrUzh1T>vYWjfsB z6(prl%;~#?tV}?A7+N!%<84TMaea-ELj{o@s`G!yb9l%!mp|0^#k^Wnmr&+V+Mg2> zRRFfDZC6Y$k(`~_LOTaCha+K=93AolHImH_pSeNq8M@sU(=?q~|4H=lG^LH85T1R{b!L)Yd(HT7tK?>cDjm{swlnzf9K@K&8vyr4 zkEfn2QRl-$8R;ULw^SET2Nn_!%_0Hrj3LKux#gBFOQgq9oj~1t;(pnB{Vrrndn}#F zH^Q_L2}66>%CZgJVqJbPlGu6W)K3^8gr6ZLbBUQ2zmKNhl0;EM_M50QK;Ihyst!Al z0m%MUBb6sl3C)1avX{z+x)*I4vz5s#ty040d&@{bJ1W^i)Q3U`I(NMyim2NBT^|aq z6k_W{9jr7Q;y0)|tulwQm>x)R0Zm3R-__2K)am{XTTg#1L;m*TU}p6Yb^mj|Ds9fB z>@pQrmvk*p+dgU9o6YYq>UQ3ZqjG!rKYI(Ro1A*EGbCtxp;|_&`<_aQA2)XQaPt?+ zFztJ(y<#nf#KUh+EdeZGi40%Ng8SXHXs%~sZ;R9rZ{l-TH-QA}qVeX#LtqFZ9g<6?Tb#*pwV0#Oywz>dz|R8IkxjZ%sbeS z8o@nS+48I&EsbGV56sM@WinH07rPm`lcNa8O4`y#c>RC`HMK28w1^SY+Ll4xR5jNP z-!)?}t~hKC&^n${_&vRKEhh&Y|eo+uF~zN^0%( zPq~;MrW~^vk>k{wZ_^lq7!#8zZb^UEC6_PMdw8bA;0)9GdWx>tNs6W@Ww-}tw0^#w zNwgzg*a7!sI4jgfKj_cZVQmf6cT9n%ZO@g944-e}9&aLzb~}C5Wo15XD^hKNDZWw) zR&5W))h(T2S8t5!!vX1%W6t;{PY*}3A53t%J`9v#!J~%hmF?o5p%)LFY-zl=xC7)X ztf}D`Y!+CYD$omv$Yt8g@^ivP+vKH#i$%2r_-38xLnyby!qQPwnntlS+oLZo7OxCv z%JRR6c1#3!h?&$@;d(d+!8=gMG2D^aS}_GgP$yKEiD|XJka%e_^b|&I?DSWNvWL1T z7<@BL_)u-~t}58iD^17)Rjiu-5)}PQnISKIT^sn3N~CtOhKmc{G5&Xor1&hYrD^D< z4AaKP8&g#U1xmUtFP=l#?*{OsWn?Y~NP%!Rx+*PP(F%1*t;U1PBI9hvkmoiz=y=W??>4p$Q+8_%=~7!Zai(thUMZNlbW9htBK3!R2%ZDPTFH zp1})Dte*Wg*tu@EHP*|%u**3;g(%|d6v<>El)OI5htNs%FM%G*68!;{$xK~^a|>Ho zm=QMYISv2*n=9i5`dDxr!A5xOeS~}e+L8c=`x_ByTdnv;vXPE6RZ#?WeRbKQ(u34@ z?x0UkEV3Ei|6IO!1iL3c__BbJ$F4YIdcsaV5ia>sMY-fq#5}8#7Cv{n3lFqtoIl3=9nV{&8`VmFxK`e|v30~}v z%X0Fg$mR)0ZXz36_8_xgpQ2i?2g^N?Y)f)b!WfXl2=?|8W-5nY)v>t<99rE`ut_6@ zEb@f&no&4q70ENp2t0HvhxPK#;;+!K^7ZqIQCMXaj@4K2A8uguhw2}JNRBn(%7Q;) zbO+P&*Xzqw=u|R6LpM*+7Sks%+YUQdQYdRtC%+Bg2@I&MV$j|k5&&QXh-8=U>s_7c zE0ky7yLRa^ZQpAzy;PF{-~GJ)1&OJ_15*bjb`)zjyYylPjXK0*-3^LMU{|}n|_u-SF|JB~NL;t^;d;q@sk(J34j|oEWCIa8k zorgmVM+@VW+t@XOJihISIb{q?f*3%8>2MtElN9G6rzuNu%mruDcCC;xW7QXhbo;4A zXx5bAP`86OtTEyplDm=EX~7=9%TECQ7f1Vt!Fu-_?3f^v2u-Yy74r3ff!E{JxbYAr z5MY`K{lNO#tE+h4de0m6)9X+yu-=N-LduEb^)u`lO;WHOwS0hAILK%Pw3cSDud(HKi7nhCEBletig0 ze<4Ej(p=C>{TVr77LaMhQcEy%yLf&>;PD{-UUJYyYxXrVzUDw)+wD9}IzHsZ_eM~V z-w;&)b{;Y~yvQ0WMOCYmBS2JC4fup;pLlW4mQ5= z5z|*k`227p znyT1TLWOx}M?cWA1U7)oBGbHd6F3Rqx+&9TN&jJX_U!mWkB2fJO2rZ`<&fgD*$wQ2hv zVZxrY=tllhu1aeIWTsT3Nzgg_xnrEFblR=O{$x7+iH)H&sM3hJqZQz0_E`wfDYWtn zcXiDSY4%}xncRx83#^F}?uVP32A z4k0s2lG>k7{MKugSm;A|0M5+|df->keWr_;0ig7|KZ+m9T5&wEi3({ld7#kDQkkqa zIfp#@kE%bu;EolW0Q)?qQfBV^gKJ0!Y%=Fx+ux^mNFItYo95jiLYOp)Z)f1Kyk5Vw zYF6r~Xv!2QW0qiXN1j}*#XnG}=ZAMnUeRzR#?vprF$-nHq)mG1t80mM&R^6{wvfFE zjprAX2dWv}*OL5(45LiG5qqAwxIsyYN*j97%ol0CLnA>Tk}c~CQ(Ose@o}J8a!wjb z-Apcx>;G>Y{l9{vSD8N{-CqrFvaT8{XPUZlrHG1aRoFbo<}l(O`vA(unQd$y;)v4+g!_hlKdT3yT_H|0I#GVTjS3cS zSzUa|4?litfmkRdJFj5uE7UBy)pUK15nffqxHIJ|)~F3^YH}7%kH(=h*y-nep1I*~ z^@?qTN0kb4r6VH0$S^wa5Aj=&e!A1Sr~cAlwwLI>w$6j)yDSoQ`RB^f3831&roY@^ z7Mnp}C&>k86YY(Fu)lm!<$ieaYwL!3-{SXCv^e}t0{mC0kqKCbemwV%*M!=u;hnni zSTk1HBN9WgA|>dN1$SczZqr3u9)e1+td#Pwu_;xzc@XCde>5rCm3)^ftjh?pAv2d3oJMV?~` zwnt^;WdoF*eBb$`lKZ(0gS^7rwpv<^4B=&mC8qKqhy`0TW@<0kvt>nJ+XGD^`2j%i zvhsX=<-jmC-&;Sea}EEVRVYNa@|UWG|8^C za(lM25Lx!-LA>4dPTo3_W@n;vQ%h)2R3}7ZGP#@W^YO=vl05K-(_D){>6wZP!NR-E zOQFbD1OyXK5>fk+S5=EiHpTKlB=tc)bq&tGP8ZzdyOoWc;n+uuJOGRRM{@J1Cq2Km zvqLJqd;OJqN!Nq0gqzLr7UNRxim^oc0b<+pTC--acyuph)>Cddy3wLF+k7nBq(RVx zc+|wQkN|P&ift*LZPR$k;9YYg|HR&U=P!&9z{jKAcA&fli^>o$M$ur8|BQKe;Qr)k zcYC*634E{3$beN>K|QjWF?KWzK@6{U$t!k@J$ATtv1x}(^6drq7@{>>H!wU5uhN+O zk?#KRQIrQTV@k8xT5ivJgQ~Iq6d{=D!6!91Mm4~MTBASB%Jm|_=5ZBWg%^jhOPZ$1 zo?3jyj?`)h&bL}?89Q)pGj(w!3$ zQ7zmt(2-z=zOYZ~G!GK?BI!S|o7WLvI~JmYeZ940-{EIdYe_$RT4!fj zR*iLi!G}#a#f4pBL0q@us7#<1kkMtWc7?)4IFSM>G0_k031}ftpW}vQS*IvlX%AWz zx@@d4nsttvZ#d$V3i81)EOa|6$co#XK7naam^4+{fYrV@ZFbjKh+j5-8qw7nPGf&* zuzu{o?U>IBiQE-7H)(4p-!~NUkZ zk$Bzih;iXAE(pGE9g{C->eUdzoMYL>MK_AbNchj+>2^Mnw`vLN;i7vz(!jD9cxEYm z$j)umQAD8-Dsc6eN<5NSobYK#P{A z{|eankeZCbQd_>u7U#}NU)_XEKGWN3us%zqN#u?&2~ShyP$x^16;^h1wMfDSG*Qe` zEn7^Aopu!4d%d`Y-oK|1`L&Ui_|1e9zCx-dc`d#UsV(QuTe*O ziFqPS9KRkoCwyhBl5+P$^P3(I)XN>$MTaK`lnH2%CoE1s0DN(73|ztZCQcR|20 z&4HUR+kfljEj84{YHQ38KxDrBS)FduUUTgljR@Vr)s^nP&w2AGi!s`=tNBS+h5175 z{T^qmMjLZ&SqAvAFp|@ic+UTDLQTI*pjWV03r*o=MQ4;dFRa_uj%tvxcXb~8`H+jILGBz z<5*=!8S^9M%9uAQSex(_`MlP^qD>+izBwE=oTQ3N^1 z*ma68ajSlKk=x>Bf&onFJSLQ8XQiJ|*=zKLgns!!Ge=fC0QmPEn9oU=TgfS7wHGTUtew`fCgD(AY5Uut~~jj=XTD8JP2{0 zDLvRi2>rxpxWPin+6-F|wHB(lyTV&KWudS#tJ18)qM(1+ z!*2b*L$8?>8&x=;cF}H6nNlCCn)$`eeJ#$9dHT7(uoWIsd<5O~i?vRe($1|b-9)16 zg(_Ya%stBRTX@F7rQ%~KF@M4-xQ@2}$X1#9aJnO=LwSe{`)oWmjBIV(y`FMSme)W= zq#W#Q&9V#=F)Ajo3${Q|D9&dzx0Kv3)reZa#BDra|snN_xlK}b*LU4E(G{duBt^}{#c z7US-RWL8gWKr2axtc6K#ER%OUO5Ue{-%=$X6-gDMe<1oS7)Pc^cNuVD)PQcSdh$6bR4_-JK~>L=Y5orFk~N2-LjkI@MzD=^kZd{!MgDc8FoQ?hfamx5MiM zPg#EPWS>=9`k9azAB@yl#F|P>dF~T@CTl>EriYI5=>$gjO5=>Q)uMj5X&fk*@AEf? zOH>KMuqCcz$-@;sfAE)w9;H~H+>2A1;;5y;vLCY#>aKU@ThdY?*7{L4OCTz8WwnEM z(~MgP9opj}EBOPxmln7!QiWchL*qQKqFG(|iAPB)>GAeEPTDgbA@ij}STPNl+~rS# zci!|1m3WTwZ>wUz$xyO&ygS6&SFb<=@b`B<>0WOEgi(=Gklqm7>TO?xgO>I*h{?29 z6NZfP8}VkdE65I$%Ca7BlDO;bQ~k@QYByR+R3b_tO1dh$aq=xQ^q z*TZGBok5GPa~+{P zd$gr~s&WhpZR_-|h&-Pe+!2m4Kwlz)>M zc9<{_n}+j2)8g$;GA<$kKlLU$$~;^BIB`6LIWbVHw$EnDSa}dAWV5v*pHqZHPj!br zW%E`TG|LWgq{O!yqv;w)_x_R6Xb~97ZP{Cwpg=PO{GM|3@EtDevK&uTAs@LQVm+dw zG$>bU1&qfFnF2^CnGZ-T~)H2N{ovgjUr* zV`pp1c06!Qa@e*AV-9BBMN_*ww(!SR z4fX&MqXQ?&D#Kx25zGSDxT(96=1kE|p;wpNzI&!rC@+sb@?iw*ypz2MRBvxKj$;C$ z6pNJVKhq&+5Nu8vz9O3WvW2r6kaA^Z!m$z-x<22l3j09 zr5iMLI6%GDr*tXmdzUOPIb(nKM^}MVo+Y&>>*ID-S0c~nLKbDUlqtCQl%Wuk~Oij%Mk%}2v>n^isOJA>X@~>hiD$~T@R#RisoBhS9e#*|= zgB#CRte~jv8dkBQ*qK1WS*}Ho5y{p#no4jcsu#>6SPcTp%aM!Z^u*{G>vM=Th^Hf4 zt0AqB-|Uf_!zhKX)+#oD86CugNFqZw+CX85`UL8GsB)a?(9=9(!g@1798JKkuBQ|; za}RgVspzwHWAv6-9{%(L|zW!uH0isr;<@@gzFt3S-O)Q30y1uimZ%RH4)|u!M7EbnEcuB(;G`C zYLGHGkH;jyIjSMjl<(ef?<(CtA(lJVIIB+i+#5|bR_PkEX)G}lf|br+_DudDL{sPa(JuF%XByT>`1!5g4i(sGAr++yTNRFhJH~ycRW%1que=%oJ5tlB2tv!$f|sy zAP*OEpc}9cB}4}0L9*oh{93Mbqg+9ai%T;YUrWpT;vCMk*pe#0Tj36%>xI9i2Ix7g zFtHrA;VLDxNqy4ejom}tK|AYk?2RwDgz6|_#S!=YDwBbWr}f_U6bDE?t8oHqXzaG^ZGgzyPC;<P7A*E78Ckry~3*6!_sy}GGu1tv#t#?pG%=bu0LJ@)!3Wi0qh zZ&)4#KYyFx#DQ7C>((pcZsu6#Y0>B+O*z6U7A0Q2d9WdNknVdov@qC+JLP+|=dfnC z)f&IO*%5>pJy{u+TF%O4f74})baRzQ62mK6TI6%;S%Sj9hI#zS`;>)WsK3{C8ga}r zJ8(B?@N(@mzy&;mXQzzWb@p>s2?C#<$0wVjiaPtohva@m2Vbr{Qxq5ImX_q_Sr z+O9gFdG@p8Zwp%cg&m|)iKc3ZbV{nNYU1z3wxf3^%3aIVL~Je-y*}JVz1K@jW9xXh z_DH@6lY?3CNOHAsU;g%2n-CjAy?ZGW@?LN4KI(K~ZtC zCrpJ6 zoYv!DdhR4+`LR@K?HNL{`ZKwAY#GA>`ZX|UWFR)^<{+0Ez#YQUY|Qm4zgB%e35UM6 zggOzSmUFX-q0BU<*&LFQgM~a@Dbz|+q@kyrs@9XeW<93G@&g;IO#5URfugdX0#Tgq zT-^&sWk*Mur3>zU-qG6Trt9(uTt^G9bIfAQ4vBBo;cL$K04fb`%?>m6x7bTh*b%>m=a3dN)O2Nr=T3j>(j&2|%A28d5Kv^`us?IJ@t+Br;<@W|NSSGCRF_<_1 ze6fakdG)&{I?ML2 zx}ejpd7JsE-`NiJ%iLK}rDy}0oI!xxpIJ;b-|K3aUC$Ny8bfMnro|C1tlk5)j1d0# zp1YKpC}E&#tN~>Qq0xRr?U0zww54Qhy*85+&ANY9X8hK^_ z3{MI?K5)uWgZnh(4SLl?e=3A;iPQ=|f|I{m^+I5JrZ8@mPvh2N@S1)PLPm?#;V>ms zJtV6=3(F=Qw1>Doxg%J0*A3QrG3wbp)DK@%gVbxh@C5>glUQDu>o}P)ka)TeyA~He z+21lm`Zo$xLiMMMG7cVYcmOZ19B(o05XTqJ0t;Cluoc#<0v^PSIX`nKGk=+hobMZQ zDV=Jo_)WS8hfgpoU8w5`tCI;EdNTZg#r)b6llJX#&G}1{Dj6#E->ZT&Byp^lM6IvB zD!e``gggyzrUUY(x=6^fE~u}j9PfJsM+hokME2*d`eazTwH3Y5P;T18|3Tb4Mpyc6 z?}D*y+qP}nX2nLuwo|cfJE>G`+eyW?o%H^lb5DC9aw(9n_ce0jQ)eY@7SmK3mq$p za6F@#AgDZZL#K;hN+D6=U_*>{n4aJV3{+Vd7)!oRwpQ35D2BDBJHWpI&59UK#0Hw6 zMrF4ZZ%b+4_pv&5bG8Z+uJcp0?l1rD-%%xdIoiVYpAzM%<%sZ>LfgGi5<_+nq3?0M zUT7qLJU?;NhL8~y5bD1uo_*?Li}a9%^PoY_UVHIGQ>Q|Q$+0sLcBKCmU+;sh;iQZ$v1Uk|cpmL>a%d@vxiIWaAECWo0m_14ZMnF_z? z7l;W&HY>F7RFm)hfTj>C*Xx10b9-wFRdW@`U0b5a3xhPzD0~Z*j27}av9kBG2ht;h zDp-LXzt|jp*NO7!TzPv&heJx=OsJv1LeQh+Ax}iH2+Kb^{IJ{xSac}l|mAN0@riRkw@{PD& z?Z|I+CHZzz^JA0^%WR7s{V!*qF-IEzb*`8370 zk!nh(ycQ4X7cutyh$fcd*y*in;1(34pJ1xiOfnBui(UYD3&G6nN3sfpQq=-Nv|_u_ zXvsd8^99v1+}|knSPJ8Xx83^1z#TkQEyREJ{;FVgauOShnK^1glIn>B<$8HBJP|p# zcy2OLOHj(NuHHT3XFI}wfJkUnpmKBQDxBUf)-t4$lL8m@9460a<7!Lnc==r4h8LYv z&iMSY&W3#v3^Mx~B}kxRj`}&D@XqHwV@oqI(pSx`$``=M>))tvPe{8%iCs7R4Um%C zYXydctZ4i(i-EPZFbU}qB#BU`X|o<^FvFJ{R^`Xc*hTAXL5pG_(P0=&vq*Zv27Jr) z%O3HxnAf5isG|LjUp|Q+b5=pAv1~}qruel~KFQM5rUhzoDf5h;4EIjYw-m0kYG`Rp z)?rg~i&!2w*xEM@q$xDQkhc-hwDzJjb9E}`75_m_`xObPMbr^ZR=QuD4vF=+*wNh_ z=mz=)4Gz1@CM`s)9!Dd)x-e-qJoY&RzyFn2=OwOhcDNjq>=Vb?cLcyywboZ;Myw=0 zJK9g!(h253g)2AB4ajNQqq~iY#P9c>G8M1Xg8zXEFigj(##IAvmLnSH3!l>UL8{dN z$AKp1YBJ&A_*)TXT;&&N1FtSL1f=n3xHmhz?Rc>4ogf48YtLX``-c7ly%YvlnH{JC zithoJ!KST`>!&wXn>FbEagXWT+5N2TIOSt8WOw!q}46DR?em&s#) z9MddVzBwE!x}fdWAH+0MsIx}2KmN!;yx*NsE9}+EinY2+IOY4xsdlUT&tN$A3k}V> zplJN9YN5kTO{*>+z`v}qg&x&`7(&W>CG_OAdXgi`Tr(rO{~%I={uLd`r%_wjL%Qcf z!i+NzZqyfiu-7Wt;jp#HZu<-S(eg_5cl9V`jwO)moUhfEKctt<&hn6qgy#Yt@~4AYEqjLQ;h&3 z>8Ypf&6vEkH$pmw-nA zdzYliM5t_g+z+f171`0@evR&~{h$!oyP*zx+{8MG$&Q<_g(Qj+J#U1#(m)t#%n3P~4U7S_as&>U0%GE`FH zciSJl`%zFw@2?^fH&=+9Q&CZaEnU2PFxwwITzyxxA`=t3hle^_2fH9egCi`k7CLU2 zBh67L?8;SgAg?|v%hTh`^626q<9>v~(qbtkzk!&q-D$6Co7nhYfLvc8eklfshkwel zvPKd+lgOoGkCHxZgD2jbpb#wtBFmzjG2wEG+nf_afQt2 zI*7>1Ao|(&{Mx#0rE8cetKt8uo0E%0#G)9G{#~;JT&w_+GpX!iNas4mB z#s{-vv!jDgMfA)RS0&||9T>f^2}f*(0rPPO?0Sd=biWh4>?$cgnlutXP=yspR-x150R`41D05UxaPcVMx6otC7lv-KR z#)qpYTVagDp(~!(c^OI<&%d{53=)Hfn@J zX4A1vzh%yb7*hC0zMxI(1&c^qtIvXeTX#g;xa&qH$uA`M`7!Wm92xEH8qQXlh%c6F zJ3bb}$~Ju5J!`Fsby{w%T_fFcp}37)kMO*M@p-#KcXBF-x5*aV2ymfu=$LPcKGmsN z#~nrsL|m4QkW{0-+Ux?zz4#ZmO##pOmQIs={YnIRDo1@*Xx2*}kF zk7fQmaAix?Ei~v*zwF((>gcBqxqG{pWGCv=v^#gG3b}aT zK%sFXK;twK1PW{*su|iFBBFm(j9mA_(UePg#UMK z3qkNtZJRes@?MAh%T#e^A2qPlPFF~_jlBD3T>n@SqCj`fEyndP_T9x2ev_HEy~C=e z8{ZWS{hVf`?!SVBGAMJ~*72Xu$*HD&77F_%rnGD;Q?whK=s#wBci*|{ zgnan+@`2spWnCQ=gW8^Lnr$55<=xzf1PGwPtzCxWN81qJ0-2Zh%s`Gt2A0?rT(&U}ha2PXj?6i~99<)x#E(ucsp>?=5a* z4%2_XIXaUYBHmG?w>77|&M_`M@TQ)scNVEduuJ^OP}L+(98l~?jn+;DH)UW?PEHyf zwc^F!z+p0o9brgI36Zen7eJ>YZUhd5|1@qdfnj4~4>&L*!?cvbY>}5Ye%n>Y_22j5 z2e`}902mtI;~0XZm7Vq(JT@r37^|$53}BcS0yMyW%N!Mwkmwsa#l{W@gJ2tH`!THL zsrtbE>kXdSZbjo^p(7$MjIRH(=Tlp|OTi>m5+2O(1(q3ziW*v)=4@68G+z_<=*X=AQAoe5{a?`U zlX*Edhoq|Mj+Ggib4V@Oj<-^R&i0;eF)?sW^z|xvymR5!7R4okz$}xVL2@-5n2r~_@dwCdV2C_tv7gP zL0z!dKosEHG9F(*!awJc@x8D+SFQ&4A%b`LmGqWBO~2iT4pw=4zIO|{@k=>4f)d9^ z3d7`W3%<$-KTN{&MV(os^xJ0ZDuCG7@^e!H4&;C`UtyDw@o}P*UY#qwn9!C)N@f=6 z0VeS+{!gZe-QVL9nlsi`o}3h6vGS*ZUkDcAzB=mlvByKC8`9g4G@qpA=;WbGfWkgE z$b4y}z#PAS!NsQGGf`kY)_LkZlP>EYb5^`Xl?JkYeDAZl!)|BP3TWZaGWwlEVL+G} z@f3aSOVu=A#IMnhC;0XmNleb?IC>LKY}t8*px|TKfO95$w!2vyc+#MZu5K+mTR&09 z#n%W+=A$+(Al!#1+gI|v6&7U{Zu5@cH!72Bm@g%uWA_z+@oB8I$EFj9 zAu}jT{tIc-v5efMhQF^5k*|Zl1^7w<{#fyUME23@?#JelJxLg`fc6$5%#6hnVlB~ zQ!_qT5*AWRhJJVT2NOnKl+f>aba4|q?+!w!I|I^qa~~D03dPTQ-!IFDk8b7*I>?=8 zt`1IC!<|VJ#dda<@QM!p%x@-e-m5{{Z7J+W{@Xd^51vBU%r))a{#xcuuWzX9PNyhM zwcojTVy`s=2Y>Ay>y!KYyP?+~^8!DGp=Fj`aPPCD;!*$^%c?mrOdcn_8yyhQImi+G zZU3W;K9j?}{l)rDsPvYukRJc_C(A@bU1WU&l33(r((4CUX=`e~dEceTH58s|!8;(M zrmym?-;5e^F!q{-Srkj+oDuBGe26{47r^j0OVuNzzegr9Yw*ZM245>{8^n4?RDlFC z8zJVQAxKS%GjfMzCmZ1lyZ*sAiF*TwW6?ENgaM@Pi-mIz8X@Xd!-(Tjd+{^E126LT z{R4jR#& zC}FHuNp-laCJdMJqn)uxbS)-hq(- zRSw_y{yFdor>%U%VaA-G%r#`LZg=V7Efzoa!Yp=S-wr#qH?0F`#Qx9ZGfP1) zxb^izdK}BC^Q+-j zGffX{Gvjq{2fhks)V-NSwWZ4U8zow}Omw?@1Q={nIU*7g87`quaS|LSwj2SE<{+Jj zYO5o38nVt#2%9nO2F(@$fIC6VaTCCiJGrZkr@F-6%Rp^emUXATcd78cf9 zt6`IJQ)8`8xHyfhT-TqR%|()Z9D8+)rEwS-i*9Z}CCNyDL;JQCH~P~zSHj0yQl#P$ zK1D#cvVPTs!gn;E%Ch?V3q^Wfm%b{H$ts(^vF96cifFWvCoYM-(e9h-7Jg_-`)aN- z4&5?|-#HJo@WMO)B(`B?wO;iCq8E7n)fk=pDdU_HK}8!iO-~?JgPWPSI3BtFa4k_w zg8aCOOuw?+!>lQ4Rgx8v?cYJ(c7gD@NA^!mONcr2K0kArsSKceeo4`d_SqqQdXZ$$ z!!Ei-mTE^?O-2@y=E!Qjg zl{(5z)LwIEWF(V+f1kTl6pTh<{8g^Kjyk9Q;gU_DNI~**{o&zy5mnKGk|(kKQ3OUR zsY-WFYnDFqoGNX`nK8bG@qQd?b6Vs{SlE-ws0T?cKEby>e{WdCiNC*9arBJg;BJ6z z-Rl*P-j=v<`2KJLvn-t%z4;mnYk_40a-vSMj!v4_hmhRDTeY(j-crJP7e)Jl#EBc; z&qQ)q_)YD$7DFB3mrCbKpy#z=>6|dpCiUCAzZXxN)RTra^|!&x7l~8x@FPVIZx|bQ z$Ijh*-6T|C*Xc_*_S2WRRg!BWXM@SEDZu?x=E6T(p17KL$)Z)nu(t#nxPO5e@n zlX$bRcXNZr51q`|_o}!2=T^ApoO-1F!H^x&s-2}GOcTU+qaba27l#?Sz>#)VC6^2M z8Nv}EJ$={=hk-<`GWUj-;WuyNorvIBf<{o?5pB`VkmV)o?*M^X11$e1JcBsvfb~(4 z+3~+89lG#a6x01uto(m^UUB_$_<^8*A2*cho2$9HNy_3A>a}CVXEpbs{z5!#ZnJ7V&C*pZMM65rYv z)NXLEufSZ3%?hmHAe-v9m2JT`>>K)LFh>p3cKbE(~~(BtgStHD|LQe zB>W}M_gocfZ6qD_t;t4j74!U`+8MJ%X#hTT*HB0}XfWEW3rJG)`i z3O-#=xuvk;rVw-p)oP*S(keMSUXzPyV2qCt$R0w%%}R)D;CfyWX64LuxwR{3VihaN zJ@oWXc*}|^|IAE?KR*nauXH#0 zRx;NUu9E4KxHnyhqI@I6iLy3D$f`s{g`jabYeH;tH;m zM6e!H>y$2*6IHoR-$8)EyCXh~%|S+?RfPp3BOzzQ=#B?pI)e}fYAn`FfDq?o;=mshM&uGTZVf<--n3_?$OAq4}QVH%^L7V>X>k)NfJQ-w{%5Cqh@+KZs3r zQhgcg`4JbGC{?X{_>{)FVJj#V&+^#hOtRngjaH&t1*ozOLC%Ru3ZGpqBfzD9+ZADg zr@~@K^9jqAXOv^pL(@=0fNyB;c%av`&uaOcS$OPvf%&!`Xc~@dK~+n3@cD?zqU9#i zS;>K4@V?66FEj#NT*)Ff+3$_GKlcK%MceJa#)An@_lKxftonTu+(%_#7z?5hRs4#t zuY4p0Q(fotA|n3IuP5xyNTHa|HjIvkgtMLse-$>rn)ylQy$v0kD=oV_cj_i?Zg-_P zq`QY(A?4cc($-?VW70ZG%sb5+PukXoJ%mv4@if6tNVtl>z3tyvQ{P;OIwA!%R`v~y zVbQ!!s0~_PXyKp7ffHkG?Y{5;0nY^t%|4YEo#ZyGU>0;bK*(9-4QCRxfNzKk#K@9*Jhb11!o8vrE;fy!^R1zpXla-p*T@5={_jKmW+|K>evhvDx8 z7n14$8yZM{#>Zi=jJ!}KjsP+uHB0el(Hsy(BB=_TesYsF=)pTayBw;S`#ID3=UaUs z;B$p(;cK6Vx^4g|Lk*Sw%`MpNO+Q;rTC6vazy0IWr+sOB<4sz4wa16v@7X&bEt2I# zZ9!OvLy46eTIlZ-N*!I*tlZBqYnMdA`?Z~e;Ln=-dLsAOhm+2PB zb?!sI+Kfy_vBA*mhSyp0Nt+7@^~Lbx#?XlDKS-cimjO{UKz95m#lSlFRfoCe7%Zjc zYtMYA1@Fx6vmhb7^aI_{8dRS%2Qq&d(Uw&k$dB;&Lx>p(_v-X*>YJ~HEb-REf#G2& zV)8vm5RJkK3x>uCs2pqMXSJY;9C=~!7(-2oU3-+&;#uF2!aZ+&6{n1sruZs~6m%i!WC(R(Uz!|jRL zgr6gOArrW%v?4(czvE_$7w|zi3a7k*H0tw=Ar15Ztj6}#tCFXX{z(Dhq>X+fKQH_- zYfNvwekeZz7HhTk#DBTV;entP8q#XxXk;L(hRHNRWB*UK6U6{0M`vVY5*SwNmaxNw zr2Dir6rlYyz646NI08xhyN0>Kt$_Cg3J*3k#MXj%pWKWN>=IMtOnI-Vq`3j9nGtqO z^e~yL6si6iN{!;(r~@s^6qFAaF^K)#gxEkUIis5m|szL4~Hn(diYM)2DU;DhJj9j3$qz&&I8U zH&R1{>hgnccr~Z`r8P~1HGd{TIYB0$joN|S*M5q`p)1Th6+{5{9T^0f8FUW)?}m)upqc1j0jkz?GQ?)Qu-J^mro@&# zNJ{_gZE!RkSmH;haZ&SRmQrV@)b?wtLx-#2L**lU=UQF z8x$Zm+=4_1N7gT*T?L#k-vTdy5%SRi zp-X~ev^vkw0SQ6lj>Xvn>l4l>Qul?7wYr2{n8_pL4A(yjm%E*XW(j}RvU}BniLT~l zd@)*3-Z+ChP)sP8ijpfRG@Iip7H%+n)xvw*-e=^2IQc&DNt455_h9d;&yVM;5|;my z724XebpNsmOU;TOs1|}SEAasvXr$(z*)A0>({81=NaHqZVsjI0wVrSS5jVfY53j#T zZg)+Cq}otsZ~!d=p&$I)dRER4=6jpd=v@_hxb{r%WappUgeHgCd{x227ALX*D>ygK ztsut}ajthn7WliSqjCjl`iMrW>yGlGYL(EvB_MsHv*9QR$F`J*=-DR>nvR~`Y1{(T zJ@Ya2vTZ&8Av_|{e-g+H>cyA)N~1n_FdxP2cE<5|mIBYmB0!*KGIC-xvbSLB zg>)H8bdx?g{`eDq6G}7iQt90UFsUE+fABgWXUGq0Bt~=qRW%hNu-TuH1&iy%$m56! z5VKoGWSmOK$8k3!HPI_J)e2J@qiy7Jxu-to6SscqO?4YMMtFmke zpxv`r<~DnOJm|a-3Pl1_k)@*q>l5I{G!HOdxk|ccAai-%)e6d8smXXti5eyRIgfPH z*fI43MvkR`oK|q8Cg_Fj6>-VOXQ=6hezFdh)TQAf&q*#gABNx`hoD$gJ&8;2#$#>g zvp4FJ?MHHbQv9?&RllOrW75hsJo(nC-$N*dwO)nA#HhR3Y9i^S<;BQQ0NVfYL>)I= z>1YK0;^MupMwb6;tAN1QoSy>T);doh4|^+_sw%DZygA8bJFf0QB21mbWr22XPDM%* zyEOTc+4%?mquxc@iUBic+^C;OR;O-)(|yK49^f1O=kT624s0zHP*i1|tv^9yb)7?k zqQUSKfxUk$^ghLo>Ch0f;!;}ASz%pz>b2`Z=x3bDku>1ez%U%vf=51Eo|ki&Is6bQ zZJin&IQMtvq*GdCG~Y8Gk|pUO_Z{0>ioBpBGc)-iN$)k4|M|Qh-NB6Ji{Bw5mW-+x z*y&$NFm*XPDJFv9u5fVnH!byTEV7I96D}$-Y5@kgFp|lacBz0~NJ`c-MFwE#1 z{TGYo9fBS@O=StlZ659;4$mN$L5(73T7Z>kGjaA}aj*O5--Q-V(y_}#;?u3A{HVZ| za$!eE9mBJnA%M%xfkCErGlu_fICaA}M*-*1e^}{Zp#M(C{_FpNOnzLGCsD5dJJowH zeCYdqKCr${0q2U)Ad34Pq{o%WQ-EEQ)al2ek8MZxjdr2Q;m}Iz%*FlAXAVGf)f0ac zX|^W}hZIdb?H|j1i#;GCc~CO}5pYc_wz_-*?3~I!+Z7&Wb`mhY3PUukJ#*)6}b1#?pD1|ka5FWiGPsj zG5^0d2MsZbmGt+o&~kS4p%XzHdG+oHg&)ht&JLVhS~AAQ+hCV(GreE=ih@}W&wH;_ zj}?TiGla3Z+nixJhd>_$&sOF#Dfpq$RU>^xp`dgnsgAa+Ska2&M4zbpzyXu>zBjIH zVeJDDe6%-l0Zy1G@hHr&xAC?XWP7>K@h3;tu&k`O?e1AH*ZE@P=k2kc0G>PVzaiGy z$J4(9JVpOFcBx=aZv4URkq|nJc-BIq_lHyfgCzX_sl+}vC8dsDvnSv#tx8%-0fJ8Qa;WZ- z)?cggzNS>AWpppt$!i6oddm-ChQ+M>NxGq2gq0p$My>(m2CD6Gt1p>O9ExP0p`s6NHB$mdy$QE`zi;BXSKTC9*nf3=}{ZW}--LCDS4 z3Is=mAg&troNQisQBttfZ3GNu_`Pi%qFJsb=N$#;wc`^p#aRCK|G^7dhp0H*`wGlN z_z26f2Fl4XHS`g9JA1zsHNAvhmp6eX+WHx&pzR!u^4VbaVJ*gK6ZCc`J`n6ESi-X& zPY~Sd?m2;jURk@yV&0JxN)^3HHNx`KRJW1#4_qVDWfy1eVmkD#g>|L%lCMWOI>b~| zt0!b=3^N51GBP<{{uc>yELWxiAs?XoNo<+z9tIsn_HUOs8P28ZOROIEt*gbm+1dPH zRI!SU^$~N5JNU4&LZHYRn(rg$+gcNAi@mMrkJ2NvjdpnWHSFx?FWmLHA|u?}6--r0 zSlF}QJOL^)5!~-X_Gfm+a#!aA2kSD$lHq;@fmiZl%l%<`>klOvJ%agze||3bQ2r_@ zXa2-ds>da++W0kUP4by?+d!}URaM+gdjSxfr;~Vg?5^iPbaW=PWMH$~@&{n#c}uT} zj|0+eKtw?oKgK{TT!x>LvOW~Ed;3YMnHb@95s_+hzMWYma@HZXKsUs8BsQn~LG(Ax z&weEaXDvNR%tRQ$KKNt>b(ie9LY%m&GhqU8!SejjrMW&d0%xY@G18Z@dHD3!jKsN2 zUNNOg(uuGCj{Cij#@ci#m;<6rNjfB18PA2P#uYX3p7GA~|-dJk{)gcGH zt&JorMnqku_F4lwelemb&EHC`tz4L08Z%7DP;UCD|3l9b0|r@ zyAHi(M3J=CbC~Zil}bv2W$5`Hk3m|^it~Tzk0}fIX*zbPtY3+juh>G!QMT)Ft>m+2 zhcdjXS)NXqXC~#14`=9LD2CfCiECqCTC&#OQ;F#1Fd?)74a=+`2PM!5W5Dl4{8W!f zk$%YW{4|N?5yK10KJxeG!wGJ1sU_i+1259e8dKM%x(5>?jtlCNn77_7o4r}qQorU2 z@nruj^wwf#_pltIJeCfu<{~z?b7|IYk1mF7Vcq8S2c*c5Spo(sL42uoJ5$t&oKD~=h6?5uY z(`v>icsRcs>IjtWZ!TX(%zh$ZQZx^@u2{`hn+GoE3htI&m5%bCG8D%oq}$}XZH>MH4-L!Yi8i|slj$7vWKh!0diMX z-P#oJ$^V34FA0J$pi6SwtJF>}J6s-|HQSGo?sANPVhqPG%#29}yAH>gN9VfAgt1I0 z05iq>_S5hIFl6*|SWoQUt#dcyr*)OcQl?BYa%&FwrJ=~1Iz@-F0smlkJ5uv!D=1CH z*iONDq~ZRRQDEv;ILh4Z=Pr^Hl=W{?rZGp3Nj4f{Qd+EA^t&0+4dYZNrs3imp^1nj zONR$cio?T@Or4xR{;nek6|gEu@m-2J_4(;i%@5v%Me73DQc#19GOv&!##}hxNWZ;$ zdgZ}^Se)+FRoOhN5EO&i(`)sy9e$;n-rZ|NsCx$mt5^EvP624oyNHtkZMcR8=nSlA z7yn2$mm+S=Z44e>QMEJe+-Jy&o{EU|I_TGS0|lxYYf6fW18ozqMQt-5Zv}L@i1SNvr``2%LI;;`0-Dbl%+porL0^ z`#1;mdMeBNdei&(@{gwj0di=q8%2RF!z(JlPg5X9CQIN)rqL$Kj10?2oj3*D+1H!T z4`DBpxf;ms0u<=B#kC@|nNAH6VneM>t3$pw;W+fyl$*~+GH}A=j znWq*Q(|M0za(g+aDa{;EP$|oOBRt!czza4=J;Gqv^3B>Qq6}`=YAJ^ zvJi8}tjU#qT+a`9w~ejX5RHXAv0ZxwD!`dzxlQ|B$+Bk4jkT*2W;PmOz&AIM$LAEN zaS`_yBF@=t-!2wzJ-d=}Uw>znOi8i4=ywR%n#|HSR0yWC7rKOl0=S0{#97n8;=xvX|J zpsKIRHrbCRb7O!8?VP{*=w^FsA%(u0DeVtewx3V-Cd#RADR#YLH|OxhzhR%!c)97t zT}RS*iy>2nz>fzU&BPRcg?S;PD^d!~ev%dPkvhGuFde8q`gn=!+1E@dl62!i>%SK@ z@QSHwwxrQ%CF$7z3>J5>5kn4Yy??MOknTbq=&Oc<6O>T>yS|CDKis8THd4xdssXaEJu4@&>V92_CgsS1 z_wUd$4uG1No_ByQHQdEPhp$XQN`Lc*BYYZj^>%v*#PPW!;|9r_F8rs(VvXu1ty z_%;Rn&|Q}E#1FD?qwhlHlT%B?3^!RWS8arETcX!47}}%Cy#*TlhMQI6i;X{Eq;k1^ zHW85S{A21}1S!-QGZ_sL(qP^0cmcIfM@$?of#)$LSTtV2Md5Wv^reF;VamtJt59MT zE0W=TzQ)?5nA|~RVECKSHq5WSw9(aQg4H@+ur6rfwo|N96v3%marTFrSz9;+ovbYGY8ps;o`gm z=Ctr^M&AuTdIY$84G;i|4k6aUvx**b>Dpaw=5hX2Yr0jQ;-TM)Ub0#eqzDd>fMxY5 z*@j`n#U@yu2iClUFxz&=T0i=LiSKEPLN2iduD<*Pf+3)`4+w(~m~3-MfXj!F#ir0s0Aty99af1vm8kSK*|66Zj0Sv6+!z@U~4_DQ_L-_+Iwl$PA?8 zd`{d8HrgI=)ESAQWZLde3}3DB`oVwy-z!y+zQucw=V(Iuh=$l8LL?@p3&6ggAL1Em z(XWn%h9Rfi#+gY`RbTb}2e7Gsl-Sb*i8*MBiD@z&@f6`i-QU`n;4;&D7%{a+MHEc1 zQq|Bgp9JMF#<$Xy)Fgz7YtFXwdyW(?VtYcx!!m9ny}^ojdJ6RWxB$YB&2N}^RZCe~1Ix=C{!POx zUI42=b_Om7RjQhEvy1S4Pjn5C+B3q55ceoO@(a z;>%siEYe0L`Z&sqV5Ab=g^CBVX0!VBGbz3VE_!+_9vQhPuyhIseK^?wb~U4%l$i&| zDyFfSmu_&)#lmrX1+)8gqtU)4pj5Pe=_j_{gG>~J()=~uTYa)|WZs>%MXFC7eXzXh zPMvJpjWGhl9HKuM2uTLr^`}&di2z8MqM(FZ*_C5?IV`j+FYDouWVZ)6kPiKv3Tk_bWBKZ_tBkNdqF2PohOIchy4#`>DiHUz z(wU|$82*i&J}O`AJGkWE&lvu-8fN9~-}PH=7siYcL$S#oY!xJdVtxy?BM)rY;RCNM z%7)FBb0lVby$v;KiN>%w3|9dLA?0{K8u!Yo?(1RC+O4(LU~kT=}DVeHvE zx#UV90v)?Hx#fG{U=T0wlhMKyq6tF$jO)B8`hyyI15gSJ!b_WEVv?Kv zp-F*C4#$z*slUJfYMED3LJC9r^`DNMO38Y~S|Hl*oK&K(;#Wo+u$B%K1VRAOiy3Z& zs|q{KvR8w)H2;k7M2?vlson3oz=g9`rd2eMz{}0;xJ z7i1LaZ7tr(Yz*atz#*KQHe6H%{(>11$jM^o(hb}YQ8y-+!aRn|@ZI#dZ-fbFCvf>u zma(uN?jK_ZkJdj#!!;yoxV>j09Rd;bxr|K3aZ~2>N1d0i_VUFP@kLxMXAiSWk+O}~ zj?K9?3TUXDP(hT=S=RU3|+gskavV1+hAjQeh~Ls%hF)&wL8ZW>ec&%QO6P<3z#EnhvYbM|*^sJux0i zLB8)M0cB67FO*!0FJ^eO$P_+c92gOyGCfz_x@<;*@k@z}0u>g3Xa2;a=luxyCF|Y9 z^2K;Zuoh`XVZxHgov;yA<;=LzQ?wzVT5e(E!oty--`Z6+AJ_YWlBX8Y)s=SS{Zy#% zlxjx8On$DBW3?mGokXPrxpNWpYLo=mG@s$lgjhAX z;bnw_*1f3B=+E&B6>&m^Q?jJ(O5%evOGOq|=*SY5vmJr7r)^m(b7QJXyaoUaktNjU zeKZ^J#PTbO+>D}$QgVg(;E=M1Z?=bBBV{Q`Xr*f;L6?%3g%cxba5FU4@Y;rwyy`z0jZ#%tT1aUm{*Cea}KILoxYo^1=)#V=TY+*VohP>45oF$55)1$}>8zQI4 zP`_|#-&>W}hFV&#dbC^BKeP1|XXv1urizLjbHn(aH(G$sJbkb1&w2*+HY&B{u^eEP zpqNCTsR8G z%P)xzF1{^w{EB`z{}!*xO;^exf}e-^%Ct4iq1FH(*Mgj6U{aRC)>iO%lK$NUlGBK| zN{>SFl_<7CQ=**|ZPW`MVT29f4Bhdae#3G--Cz<0map~QuFCLwt_j& zp}&6^rsy9;M=UXAZ#LtmSW3q|5$)v0HxeF zdO2P`+lF*F_77M*mb!fuKg*h_krGDDY#1JJ* z?H^x8=9ior`AgKXhO#xS<$i{ykh31|^dnDN*HeYj)aj>1a|3g5!y-yeHl#_^ic?65 zTJ~s6In*CHg~5fghS3@>=4{21#-THd9ogum;&90ZA6o~m(Mcl;8~9^DUpnuNeuX)! zVfLULC2I5Qtpd1JQZ!Xh9D$(T@cT{qX6bFC=w~_-mtvu1$FO*P5yL7pAm~ zR}>3Pul|k_>KJ*u+3s(+3n(cieY;ez#-f|>XR>y;`p}L@?h8E$qr=lOsXC}mEu(o# z6x`n_$?R7iVR{*4n_K+2zW6o7DLQ=(*K!vK(r;WFO$?^Dqv-A0HJlg@Yr*2H&W8)Vy5ZRqYb=e}N_OmK*FuK`^2`w~z#b0V8kC z4-6OVRzV2AuM+|VO87*WQ-)^Bb4P>aDc|>nuf_c2F z%T+Z{Yt{OM^U-d=2{je09hM*HOTS#JiVdm#CSP%6=Pyg_SrgynQ^htQpgZXO^&k$LDW4a3_B? zT{3eJ<82mK1roxkWyB7Nw7@Z$46-{>y2iJ^(h5V*T~OYiuR9sAC6ndnhB9Eq+xM#r zo`dByQr_3&6X<{tRKSj__QGVI30q>q0Q`ANl-SqXphx@u19$f9g4s3iDa+wKrtxy& zBfp5!;if?82eG;!PEqggALwj*1yQWo>d;^?E2eING7&d8qqHE2!}%){@jXn zj!U-2J9j3hQ8VT>=Id0aZV9|n*;*{)P4+c?PxVHS3tO&E!FQE)7FBcuXP%}Ot>s1H zZC~F!cL-Q+b~Iw`hlD*Z3n|*i=l|HBDTNOos31c^R^AiFp+TEpH9dU@47Vu{p8a*C zUuH76a3w5)qHsg+M-;ya2D2)V zldE+*XGe1iUPlZU;(*1?$u{+*`6nmK<9{NRBp0|mP4KggPDbc)N{o)l6o!ZU6Ctj+ zo7w#kx6=!o{R8?;P5o`T-ce!3U@Jcn9F>WDv=thHp?az!Q9x(hr;&OobE#5`D2e^r zu)7)`*4audTam281z(hcX`t*L*_cA65gE}~c+x+{B-xx@ z?Qc^}la`Y8e^B?1(UrD+wr`w@ZKq<}wr$&~*s4@)+qPA)Z9A#hw$FN=cfZ}eyZhZ^ z^udSob&atw*1E6zy4L*7Isa*rnvUlCTRI}Nfsc(W0$WpC3&meFx2>-J$*zb>XQV9= zqlQs%=<5-h_GV0BLDh{fY323%lDT)v7n1vpiNEHcsrbZEqJkFgQbziWEHNy@i(=c{ zLq@YaqYs^3a+B4HN#auDs_yKyd~czAjz3aQp-P&jgsdI{yxYNN5FUjbH=p-W)=E&@ zLnS=kYfqh$%+ex`*Aq;e&cAD2kdQRg4O83%NQqkps;I8p%@cFCZp5u`c^(&jcU>IuZZpOU#rEV_;OP@Z3*O7*QDwrwSw0jcxjH{^JN20Zg>X(3b6s89%()Zw zw!Nv|?lI<+5TN=1xP~AE=Y*F@r@qBQ&;~$jUj|@(Zfz>AsM{uV|v~ ztUaS6Fz^es-Y6{!zy%6m)G`|cvE%KH%=m3+gu4Js@T}jEL>H8J`iikp5uRA?M_=0w zXk6PF2>NP@!&5uh7p8mw-aownCY5GAeqr20AMzMJWHkd%lZ)4Ve)PUKDg1ZaI2AOu z6f+Bf-sXw-PumiTcHzZQ&`(&hJijUuzz_*h^}@lZ!r&IY(<2s2*H@ah2~KwfEtES# zp&e3D>cOeK8)kgwMKsWH<)np_&=nfYs``GZ(il(zPl<^TLW+4Xj{&OV@LCWz;jDxq zZ+`ndlhb6d;d4z+8^nwBXvp)j*c1k}e3~O&Z$U8W5|T3^_TIP{Sk4NN`cXDTQ2MUi z3wq-2DnBUjvP0O#p9$*Mtk7tC16_)MG#hgfpZuZV_`1j6L$DQtv^Fpl0E#yVJe8@J zVchBA^uDv;?s|DdX#Da(;1Tr@Z#-K_K}r32=S036X@V6U!`QnwSi}|)|7chnQ6}^9 zqGE})q@lXTS9zf$pvjpU4a2v7qjF`flf4^gTW2}L#1N2h@2uYV@!mr%kxwp%e9TP!(UWzh`{$OB?XvLK~S^SDEz43`#h$F@)H(fSvb$n4G#Z_ zHwZg~J};Z4`!PK#9Ohj_F3wM;uQWNJ9K} zzuCRMg6s6X7KJrxG1kN3yAfS-gCyX11xNhK5JAL*oUkI1=;o$yDg*Q7u59BPEU`l) zzd?j;MN|0WtRH%M9InJgSJY(JBNRC_)G^>>xLJtFX9)d?Qt%bAyC}L`Moe#rNJahzKU@##`1>O016d}b5IbvQ@79Z19Fd-_C zQ2-(;T&l}Su-MpAWM?bvhncQGKw7uq2N06RVS4EPQmHp$WczB`Z>}1+LM6*jvw*A5 z5eU7m0coJ0WNR*GV4Wo;4qP->jpF3d-*vXpK5#Y7TQDz-=0Xa%^b1VKUq|3!`jwZI zKSwoukb`S8J!|U}ulYs+GMt+lmmpcfft}nlrG%)aV`0eEu*TDiyUiC$vus^YN%xK_ z4V%hriIg}Ek1OUI8-4G$1oLa`U4LD9R9crjLiq0pMgV_ye=RTtSIZ1)Hb1TC^7;18 zjsdW77?jDT*j|rtuGMB+ki7nXtK9q4OFa}0h!HZi@^Qgl!i;0Ljv$c`9skkOi5R_N zSG`wHb7dTgQSt%G_2>4c)A#r5;RVy>{n`5H@N7G$K+{dxof!!P0!*0Y45t1rXIQM- z`=dAH-7LN+@;_+y$?D5v_uK`#u%=^!B{II|p^U2Q;#lF149w_^VLi*ypfihGxI@Kk zfN(9Uu&AB={q-WfQ5>VGg(pprZ_-8RmY$8v@gHr8$x4LI=;EL^Fr{OcXK3d$bSgjt zj+_N1F6?NCExQ$l454})s4id%Juj=2hxz$s(V4y+^@wOWa*u&yikW@f(+&&WpH`kQDrxmaz&xg z&{l?wFbeo^7e_}&fdTC;qn>^LCe7E6^Lne$*%?2UgI{y zhv_cu{n?G4p)aY~Ko5gc^`@E$$XkI8HeznHw+R%{MzGn$2sOO3T`?I&x;%?u;3b!F zqIx-N3y;I;@b)t5J;Ur6pA);-7Y3Bzfu4>-iwV!Jup53u7hf~F)v@DXtBTGnJkwye zBGs!0eng~uP)HJ z)gd*oz=TM3btgZ&e7EvtsK%+X?js-_-ffW=N}Bs#-hEm*(*!O(o#VFp@^QFl^)Jip zM19q*Fr?FNZz&75lLk}EFA7jaIZN{btkMN+-noImlh_3MAp{QB*(D%Wd|On^SWHUU zOF;2ybVCxO9pA!$z5$6Y*MOkv?W^Q*HOk0J`u&z9ercx z#S5fSNZxJWz@6d`_?2VDIWk5@$lkKzcI>Pm3n?H4E#+<5KLCQBZg*Yc=f1;vkGcHi z4N42+gbDi7pOfYk*ljvnEU_M^*X5kiXBw&UB=zTHws7sO{NVW2d;{-_11Ajfk(>n> z5Sd!-oM%K&JCwytC06>VUDTOC`yUZFFIX(Xf$@UwosH2>jz_g%I|ym%6BL)c5^|vU zcm*L8z`vb`ol>!>?>cvWptjP|65+UjO~=XzN_vt*=W7q&ag6H=CVKHyw$jkrI-^^2 z&-%smLt~v`hq#O20cE%CJ+f1J%nnhLm`&xz5(&_pP_)mkndX4$KVL#n(5CFhk(joR zJyWra7-c=YGjp5euG2yu{6yi7L5u2kS(q2L?i}f6358MzW=1< z(zj3(CY3K#&B2+=T14~dtX@ie{pUlw@wP>fbogGiZBzTW7$353>e`4R8IKng((V*I zU)m}UZZj{^!V!Iji2?Q(r&h?J{XtNR~ zJ}GX{BL122*5qll6v%tL#|sglEM^oN0QS&0cXQ|gYsCo{P+*vcIr_)2_D=Jc=ABdS zqABkNtdt#{vNB_>CBGU0qsTD!)iudDX0|SgjcZvhf;iMH(eHD846Uo2-me}q8$Yt% z0hiA34|l)?rd5Gq7P$r0GP%b9y=|1MYkX|7CCZ}ke0&HHEwR?Gx0=y7(hdR-0$^|0 zcyR4trfSp(`XE?uFWpTCc2@5=Om@MyOo2I}Y7p`D05K_?JWbx#9Jz3#0LdJ;>-A=w z*6HkrVHm~AWcjDS0GC$2LA%H5VeOJ(i2s32eCjofOcP2URmkgKU+z)T-53UXc}Z_d96&q143&5;LZ0tSFXr?OCkuKql&}WhCAB#XXJ4kuW1A zgiZKwsOcnm)qUY@KBfurYG)+W)qyu%@Fux}4W&F63Y}kcvTQeCxYW0hpa9I)EvGV| z>aPdm)TTrc=fjOfIapXtIpUwAfcFbSZ*$F8V^Rh**e7%X^yS7=ilAf#4*y?|bc8WK zsz&ET0L^V)r!!707d|Q-084q!a(E!1CLN>PAStY{eV=(N$fnSUso9mV1xvL5=L0@AXJq&AE2;oF^51jro^ex5HY9m za<;(}(euBUYJOJLmCLAo_6Zbu!{?6tV*Xo9A6uDdkJJCj%9eO?_sbh2s&MNCB>fS0N{43EO zGwF2H*Ck9X=1TUWvWSGSj`&UP!*Z?I8daGjQo~5NRhQAz0meXeGsg2ZD{1sNG73OTN7CNzws+9`^SJ7Sg*V7`JQw9U%DuP}M5 zf{b#$+@(kLE>YJ6Qb3ySkFzHU!yo*VrGi=$JzVK{(U*$|Wo@B9rDO~nrjhp6%tZ!X zUl1UC9QBoN!$0q@sdb@MluHD7ecH@-Bx>Z)XvS2>jJTcsvloGQb%>wJ(lIQ$@&B&l_bWpEKa&0b|N7QpQe3ezLrII-$KAJMM{0q} zCldXg-}N*AWjlj+#15_($TpqH8bn$Se2^O@IUzBVf^r9W?w2Zs@*dfrZ{LP@y2%Yq zmEHl*1h^f{Rv_agLl|U8ew7-$J(_@*Cq<8}FC*SFwSt(I%B|^B?-H?pMk zO%%iFK*$aCUdxx=z}%d4NB@V<(_P3j*}DnXY53h~u&t+Zx=MzW3=GnUt%sfW;V+_M z&&TWmI=TS!JXHri5jBVyR#aQ>A~LObSH~`K37`MNoQB&4x(KN;W7QedqcMH29j?aZSt^ z5}JbVa#vUIp5f7ikuNW^qq?G(Vx_A7thkZ6hL9v_G2IPHPB=3OW)NH@YGw$ntBT~w zC&l3f@pq8-Rt3;b$W9m@F@aFa^NV`M3B|3<$X*s}e6n4-i-C_&Avhu(JTVs|I0jd> zLQUAL<)6*8t`Ov(+N|TzG}BwQNC1BD$(Yh}r_LN)W7tosD_oONJVKaR`D|jw6Hek! zHAaZ>hz;ZG4t@-M)W%#0EL{}yn3g0?D(GgeQ#T`yk* z-Z(AwOYuY25Nr-S1Ujn#k8NUnCF*AS(Q{5C827Pnx##N^-Gj&r+0%;>Y?66L(AB^D z&{B(_@VsrqMrt4j25)Z6c(rk-pW@`E{ph`6(a~r6hBD1EkP5y&lefjau~7$0%~Mh^ zRHQ#!LI)%Q(9{8AMeP~Lye0)c&PxC-Nifo4CH+0qI8$w=(qEWH{}NcdfL>$_l z`|#@Vez()usyYm5aOs{a2hu$7$8MZcQe5=qIE=B1)Jluvc!APlQTTl>JTGGW0OAE6j;15o|M;1#m=Kz4#sS+U(kuXThQUwX)n4)<|{OUY-A?b9t ztZ*dxc1S2mj^37Me;!8^3WGQ2)6Jz{GBPxyy|!c*(7xiWqR^(w_fh(m(dP7yR<@!_ z#>(|SE&x?|!G{EXvfL1;Npbd0qz??t1$D;shgx*`9#8; zjXFr7E{f-h2ABrC1;Xf&r)D#&;TPt{5=oGm>s|2W#^w3)5NL;tmIJc(XN%A@B_>Nv zQ)ch`W47b1rJd>>F@>z(w<(otK}KTWhJ9H!n&330iWd4TF{?VR!|M+sOhspdlKMx> zf)ZtD&PPox^c_*5EqkIZT7_snjr{Yn@9kf=QaBazGUzHI)bu=MNIU<*idHVAfT)DhiG>DchyE|&C0`s|H3sI5Fp2! z$0v`Htm-kn(|!E#S~XyE^@WtB*eTy|Wm(XbruFyNh%bm`>rj<2u4)n59hsRar7s&d zG9pTYX0nqZYMVA76AuV^^B3iWm~gxz`3k-I6tRhD%jHwu?5yVf&|h@g1*EyZl|96- zlgcWawNp{tQ%b`J&uf_zHN6VzO#{95>Pb}ND@Rj^#pV@R>BhKVa zVHikZnxj=fB1?J_w0!=UxEpHsSnM3JzO_Vr&42OaOkCLVD$rWOQEGwvOlhA$Y6P@^@e8l?>Ofm{PSqQnLD|vg^d94xhEJq+2mA%jnZJ_W_uDcq zY@gI~#=$_)qwC3ni|502{+Z7s-fPqYJjMAps%}_qU!qU4$16Ta+NYpY_A18v3QJ;U zMrcl1mAVKbHDDkp4t_V{DT(BrXG|0xJ)Ms1`ir@0Wqoi>8a~-!@SF?x@+Ve0d56B{ z6&4Gu)ZWoMZtvx3OjTe)S|~|PfywkHX!!~wU}-3OHewJ?hF@Gj{>5bb+{1<4Ut-~& z$}F~pLMR|(P7HiNZp6UrjCgU*_NbYDo-sEeYy|B{e@;n&!M+8l zm;J)DF&9gdhY_I~>-Dr)k$|!Lm?r+y31`wKAZJ4OYwxUQF5h1V&mmNPQTSm4vqQ2P z(x)>f?T2B)VB*!-yK0~gphIZruMQy$1Rl;g1G&-^L1^wUU6Ox9MFX9-@PZk*@dw08ocNBr;C@l-+$8lD3|SvE9O;1sj=sD zWjo}%`RRYp!@~+ph?k<*BwF+P6!URWuCgpYxMUQkuF;X!uorOt!VtMPG?OwRA*+j{ zxQfnPNJIa;DPEBwj_cE`{VmM0pdqoj6oQtWf+Gh|Dn|zH5J?e{4j|@ZY=cY=y8`s~ z6Ir`xQ{#AqI)|4H&BMDIpsurTmbm(!VE3N=(?cSrqp9_YRJ>U~--dOTd2K;h zBz_0-&WPrd5DqP3Wb&w0Cykj!@Uxmj8TT+QD_JHiEbK*_sc5u4xGYg~%cY?T_K zUBjgAIq~yfr6~Fru9VkSbY;jolV_t!RmMgq^Y=@Ev=+4``gS188a%?3E}rUBR@>2SHRa z=7BD?>E*st_j8j_(n{z*vlwV;+m!TK01Q7^0?Rl=62Zu}FRK(A0BuVzq}i&R+0p#G z!5se!$VCYO5fMb-Tl~y(U}~Q)zcr2vG+5e)kNhS3nvd4Cg1}l#iy|&k-K2!r{2Bvd z_BlOGd~3EA__tDXoi6rz?^5I3pEkzCs0)C9fu9wqX>pAiu3&d-72kOop%JEk^vfH+ z5&G;wRRYX0l@1n)Lc)mgsu({W#h&f2)Ch&FxqaTfdBF0HA=_67!9+pL3fl7G>RFBL z=!CnSuo(30A&lrsZ5nVj15ivQyNLAp4*r@YNdG)*b7i?PXCGp1-1|D#O2NGCA&WD-tVu#g| zQp6AdLNXlLYI*ru9IXBDGs=#ldieH-!2$d=@&&#hwt}Wqb{U)UK)gPc zO34gevWDzUe|b^MjOUGL<7?t_*k2bO%fAyDzM5$!Hr?=&6mH}3oeGVOH7qL&=;@qf zV#ik{caHO)0&qX&T#k!@RlOEyZyJR?pM7O;KfhVLB-Q`2)qu zas8qObyS>2yk3VCAQZUatd(!b5MA z@ydv}A$;-^Yc=xh)-`&Eb?JB7BTf*Mj_Z$kbO~$pzzfz>pt2VGh*RU=*=0HeM55%K zHP^!Lv>Sup8oxAp(~UwGq1>4Dff0dEp6p{_Z=!zO-7-Y<@4SywFvV7y5eCM^u>nC% zXs-Yv(K7liTTYyCd}GzH`VMmNMWLhvp`dP#NfGuhGa=w<|2Y$4@qf;Q)EyhIAi^!q zA)jr?QmO4T1QwA~r^6|r*{N)0q&6bPfmGex@vG-~O6|G)_|jLZ06fTq%76m6Gm% zCS?c19er#jqrILFxiKmozF8opXFJpY4}yq|4OebWjfo-RqOE%IUCDE!1iTVrM~SV- z+E&$+&9V? zXf;6t!8Fd;f)<__pPvb9=o;f%5q)Xp)u`Ba=9>KEd?GC{M6mua4!4~qnY%z zZU6w>%nD=N@!cwNanW|`Y*|oeL(W0 z6+sYGl-Cf?OGcj^ez!F{^?(!1Qv1=U*=IFC(z6+oo6cPD)|Q+F1PFy%!xB&6&n6hl zu^HS2lXfq@FvK?p`!72V^3k5U7A6@S8rq_SrJ(F=APFfUVg+aWNh6F1zBb$0x*{_4 zi)XCmP4!&ara5S|+#ZZ?gIU~`7EGR8L+z*?gQ?$yWfL9aR1H#iY?BGE7V;rSYJQb=TBM#rpxZ!*ECD;GV znvy1eZs9?O;Gid$sll~L^t8Kyp~eU#zbgR;xiSt=xoCto9GRi7y1DHMOtfu-YWv*; zw&E8M(|GjmD*w%!IK1xU2V(5P*MJ264ze^_D$!K4=Bn2a-Ou40zC8g7e}2e&FAHCG zjs`|N`!2|bp4DSPCUYDvszO7r$C5%?j~mRRu6Udv$8UNHAOInMT#FMDV$*`&S>j0e!ES8JrKq#(E(bT8j zswD9#OE zkeZ24UNjg$&NhbB*S$siZ-kq_G)EzS$D+d7W+m4G0eOQ9(*i5a^&^6-vrx1<4j!-c z%GIJBp z2>2uvzB`x~$8}fn4r%2gt~*`4Dm#_c5aHE5ruR=`9OgcuAa9j(hX;OumR$cIil;YS zCU?wp?_`6ecU%h>O$7FUxs34z62e*}5Tao>8V?a}1~qU76S8g6*dS2j?Dn z%w=xy0&&*>(S9qyl=BkaoSZHrx#)-)$`!zQdSE-PmS86uqVf)o?y2dU3fG>a9l~0*pVP~EPgBn$Osf?cF8*DNC6y6d3u2Fdj9vHI%JI0FT zLx!3RiwOZ4d`>(1O|+Fk!KDQWQ7gHK1N#>Jm3nn2SrEmsOESGL`f7QmKdcv~e4~an z0rRIt`;zq!z=bQJ{{~Y%yZ;8JObEd0_Q50MXJIe#ZFiX6;EHXT1{#~WLOD86-|`1B z-=aQ3lq?zZeiI-CrQ|I;oIV-XX4%q?3eWK4Htbl+1xBI7<$FG`$3S%C2M%yR~UMr(%U$Ez?z{C`k&@pBCtFD+nmeSzs$LGJm>tsmvgcC zuQ?YG;IVU~WpdJU4qBY`h&4@VNh2f4X1tB#br)aMieL1O+Z(x2%8*Lr+AvH=e~DJK zO->F4t$@CC*HQZB@6%BpCZxD>;$u6~pyhEgz7@f-6$QMb6C@ok=(eIdxxhu)TL~5;qtrLLGJto};cvV|$S2*7#@D0oQ^GXk$`s~i zF_+16!~B&xa`h>muWldg`na=)bDS^NaW|bl3I2ay5UTI@*8#QK@PcLV+d&)eDW|KIuE|9#R$?UjAIjZ=;i-R~#g zH&*yOUG)u+*O{nAAW48XOqBpr?0vi#g;t@)i)*PNIyQ9#A5+L`>{ja=T!Z*%<{2>e zscX#iQM5ca+T{iD-46HptWn{ihb3pf7&;)gM9WugjYALW8BcNe(s%cf>$syXhC2>T zK2%CwZS)`S8RfwIq-6Ca;`>;>spA|e<3XLy3ct5Bot__i@hWgc`31^|^Cu(w@2Cz( zPFjb*$tk$^(F(}nkWuJ>lEx}m{!!`63J!@Iq+$zmau!#Ra>=7{=BoLeTK2-Y#>OU= zt<8i1O_K=qk$>v03$7t08adN zB=fE`S%McooB_r84j#wSwX-2jL1vg38H%vZF{tu#N-I z#GE>+)$cK5a}Mtv5>5o=<~A*+FK#VSq-KB4jnXb0FGZX7vP)vdm6kgh<2F_)n|DE# zKT|R`65U6zmGEb&7yXdzu+|C7KdB^#xKkoe!seLFO_|0I$Bm}|#3Tu_HT3okYaWub ze`y2EjLmk|%mrBRy%S#aS{T;;KKOFo(r!Y|1UzJ6b>i0}!M=W03V zM=TeZzv{K$<0W@j@g2gS=OeQ#4I(hs`jA-6Ff6ZTtHsjfwRYFV()>iFI_pImsVa2V za^LWLtC%(IP_%lI`s{52toRX*6zPzww}-YWuW6_3qqoY8yA7u+&PQ_TKW?p%4F&p( zZ!w5M9l!V2{YZFrW4=c@!bOh4z{tK z>H{HlINg$V32KbgUhJ#CnnDh?Ve<~MVQ93O108D!hj=CfR!_b0lriWd;c|S^3u7Pd zZTKQT*j$buPGHA4NgS^>1Rr^$2SH%BFls9FcDt!>UbQ^^7_!C>(KX3`d&eu!!n0g% zh6I07)~I@u#@XBU8*s&Ni_H$vTCkPUlH%b~Cb^P+VLnD#i*Hwno_wwfS)q9k^bVq= zu2>>IcKb@-fdCQNyzg4WqkH7UkRuc;^r88tbEY|L8aZt6W_dz%QTJ@R1RQf*Vs3Q~ zF}3i#Gd?*Dxp+rzK7w~-)X&IydnZ>(A2B%1((Cx8OZa1yxBEI?%x69wJ}<-^zXc1YKBoIT;C(i(a2$wv}xQq*I{bO(VYO8!Y{z_;twoy*NirJxY_I~$E~BqQyI z>2JV`puM`(| z1wM|kYI=8Zxz?2F;)oAb0z*?}R)gbZC*@tRiDb-ToM?bbgH_kqd_!gVtWl_z=X;#g zSS*F$=F5lngJp@E>L%4eVoFHMZN;2`i>4!+=^Jm;ZpySrq z=BBu6`BU7huA3j;pe9_i;hG3QfV-qD4*drC*}<&l-WT zm2Xx>d{#=TNe{P5Xs50U3BIjG$aCZo2#Vb@uM&$@e~Nf~Kve?}w95(2%@;jyvYf*j z?^DpRZ^Jrs*&@&K(^V>Ry?J;NWAt;&*NyVmFcQ-L$Uk@pv~@{5Q9Pa4P2bKkbrVNd zpc%haAZ(XaQKirq@N8@(wdk8JTCS?{o+Yho$Rhb@MA;qJtWEG1xccGeW@b#NEzhx; z>DQpc{5Vlh4(5~P;FV>)wyo;o#L03=LT;|;!k;QXVLw?THY+*RU(#gF^yRxKD*VlX zmh+>fFd(M_ddlDG9m$q9g{+0MbgHc8jEg!4A4he~*{?&-<#7yi`sHL&W$`Jv9Nf!K zanG8wf@<&6yUVI|QI~-8u`HR>T@ZZPNiAu(IOZ-C8|SzZ@Y`ixHt||nU|}qpI|Z*+ zkXM;nrk1omF5e+H`&MpbW|X!rJ4z&03|kHV6gW_F>)=99_;djs%skteee5c?j6Bdv zZvLEOfn;6aeQGV(ao=;1(#W+U5M=d9{Xxisl_y0|r@YC8i^8MQy=_}FmQ$~KD1Nb^ zeZ;52oR44lrfshl#TPK<_mSfK_MWSSE2!G)#eCrLdkuR|06dej8XC7n!E6}1eC}vV zdGEUr)2FOtTQ%EOd5w;|2F2Z8^w^!yPpUK`0-}M8vHh1KQ({K9>3W}4*Gp7OnSlCN z8fg>`)(f9CT5qZT&%KkQe2#WsY!vMlQkzY(S1(cwH@|fQt^4(02LWy_pE*bjNa@{6 zeX@LwSsEv}b^f?$-X()YQ%0EAq(Q2Txu}S);Wt5)7x!WMjZ@Y1`~fGX-*~pkIP;D@ z6rP@|H`g;Q-Y=SveYS2)1&a7IztR=f%~0Au-I5JWd6gX7rl$1ijC33LRVRTGf@L=J zvN4IDEhVVU%$0pFAI5m`jeL#osws559txAQ_-u2)wbj$_aVOWKUuuaX=dDW^3=QZj zrR<7w+Tm?j7S1m9aodhXT2#L~taF5RQQywF%_>RR;tu?r?kd`$53u(eWzd(Xgrds| zzB8;7p|j2IZ`8AOufNCOLudrbuJ_Z?_WV%`$o?f@>aB(1ymjwod|)-qxZ#?KOY@bO zKNuYk3Ekr|N2L$iD$YLr_8d$#vLyHFIQjT+<>P1xMRvf(zf6=!xdaKNpU4TC`-ste?Mm}5`_cAtm_q@+*z{0Z8<935m1z3XdIJU)dy z6N`%EjbBWAiK|z-2CF|^G~L-Dn8gWBU9pF1)$lby21F2%5Ik=8eVQ|Z=uI~b*Ndb{ zcEcHSnOR9cGK>c<)_EfRV;p&C_)UGX z<_>ORYss5eNP`^t>j~kT=^fidwKdZZy?%To2_nl2eVUw%6LrW6mr6uO)J?e(6O1y=oYzMx1!|W?0kDj+cE>YkrRF z;lUk#8$SPaQ)7#|{wE_kU{px~rgt9K(`GdDlGGUwAb(tHi_3C4+!il8+E44c`4Nbe z!o7Rk6d+d&RQK*#fqli-SRlMZD$~TUE46PkG%ha{gcy4hSr}1~;ppTd$!GjWRX*x- z`$NVYIce;MW~HC?R2ad0(S7K}{EbvsVII4cVp`66!MG?2Rif`x)QRE9?e$!I&yGZX?X*gXVpb|TDz?r%8PEU>K`rE zFM)h1%<*ZY=L!T~oE6Q|SC-S}%L)f48^X3y5}z$8P6$nudX!CWfwwM~<-QXq1+VJ6 zFY+--ZN-Njd-)suT3I|peQFg5n>s2H;LGzxtL2_0Un)BD95Ts>C}n;9{mS@pRAfHO z7Gva+A6x_CoXc2czJ96F-P!L_i}uxdU0i8{Y_xaMlttB(sOxldr1K(J3#bkD+REs%>%n!62*aNf*$)psty%I;-Sn%XqX^f_<2)M78ru z#`?Ru<+gM~=i$qy)CxsG(nZHnjaO2Cr7Edhdh(QNW?H$GtDW-8Gi!06O8w!-k zahLf7N5<0xIgSdu+wy*Q(x>g>ulckuwPR*B{PHGU??kWhAgcZsJmJAg%S($BWjJFQ zWlij1u~(`C21ut$FNoDiJUGB*tUQJ#xrtMc^j@oP7={q%w3e)Y}wT}btaV(jR# z%z4JieR>^7(vNGobsf8 zf;RZ_jB08j**{?pU;dO|9zq?zNUwt=pRsyV=HXi(i(C?A)iqKpb6&>db;?A6a^S(z zM5|Gp){~Qx`I*n&3iaDq>S4&GcJrB;Kj&(C_=o*v2|??hnZQ{=&v!Nn>CVQ1ghUbK zVht_KKg;29DowPE2yDFFmcy+9@RhxbU8=fMuJDO|1ODt77GTqFgT8An4s|OnNw8pHS?D|psB1w$gW$#SgI15V2$$U}m z(iR*YXbb6L{-al#_*;9e^%Z<~OP7W+NuHRB@p8&TPA)Ufe!b2PtlKQ0l7^A0Dbb2gdwRyPYE%U@=iymc@} zUOo}Yr(FDL_-TIC-P+_-ZEBfyrM1mWYsKujr`5K>)n6lR&d};3F0))+D12g`cErV| zq^mo*yBV(4TQzF0G_SyDoyM&>wosxqlsZ(;5*3BYIVC9k8Rg}_7o}ci7AGvz!cInZ z!zE52Z}yx7J&?#XMEv%Cy^k28Lc~}9Aoqm7BZ{})7YWBb0L7(zCkn&n(m$!;Kzxb* z2c)iqqbU5^U)EAsMf%$fL4qNX<;8JiMFr;{xisj59VJmQ+K)7O>y~~!=^s*gO7P*1 za-c&)*yvJp50>L{hq44CG%?`!)H&Ly@~z&KV40cmUxd^O`$wCj)W?T$lx1POW%~Jy zRj+hW(3MBSQRFA=9MOx`H462EDpWieM({54O7yfhsFYHQ1(;L%zrh#vciWXV3{9^d zBU`n?V#H<0OAGPBkls?4T#%o#r8A^+ikO&&239b?Pl~tH$Qv0E4-6tfUb94(kRay7 z_QQKd3dg`Vyi^OGsxrzrIM4GF>={L+HnvjfKE4mU60kEB+<};>;ErspZc-+E{^Tu* zo?+^~k9F#FB&ew4R=?W|_!bGNVkSOTTsXiXkuE4)BkcY8hP*X@ie3-R*x);crgzCN9>E2BC3Tvda>-vMd7()k6ncx`Ov``iRf-k!QvewyFsgE}K?sdSRf3iuCJmY!A{4=8fL zRpi#w?=%(_l8ZgchrNoYl8Jxtp*q~<2`Mo4QM;IYjukj#KG+nc-mSPF_7%%!wp5Mx zw56$C28U~oZ;Xls^DQzBv2;FJ5#Vr63;KKxS>`_73{{v~ABMFQf&Y;P&nQRS6NvG$ zeqQS$_mF(ijx~laPCX@5JMp?O`X2dHM2U}7E}S&kefR6-2?9mv9j^X4%A{Om7X|e= zo@rBi2HKjMkSpDyhwFn(Uf)|*-O1n!Jkf7V6U8OY2lCSRP#XdPXqf34JJv|}RkTXq3C{@%h0 z!kvvaQS4(?mS(3F`1)o}{f$3A==RgtuIw6wwlV}be>3y;JsDsEA^0lcxwD0!7A=)lHQ^NE^mr5k6*%o)A^3gXPaYoc@r1^JlonLErk_ujYmo2f; zaO8}iAuXC8igD(al~r{+j4}9>BRn8Y6ZE|M8uW0+s*Bi@%@@K<9j->MIKkPzAurp` z`H@va0*w%E|X4^)zTKra3;DLq|QVKRGbVC%m`>!LV^^LM0wYvfY) zZ%ID2j}CKVZMi@r;7lF7IS7t=m=X=MEiggey(o8)(7ZgT6Ki(_&=*8L;t520Gc(=q|9hqtj-o3geOYV=LnurAeo#e0m!(v zapKuF7I$4!8gM==1jEPc7d44&HSXsmqikbl3{>P1MbmOlZS#o`2 zbd=7~pNL0wwtJo}_fF(6_0eHtNgy>qNROM_p@-(A1oPvgp*Zp z^fMv}1TywcAJJ)aCmVBc%goS*7D?#27My~-Cowl%psEfZ&3;<)Z@DUFZZibDs7uY; zIyUU{vvB#nMp+L%UZPmM?Vxe7A@sDy!ifYqN_xze`4-36%7)LG>_Sht*q*F6PzoR zDBLbx2q?7exjj8`Bi0(=3X78(E!jiR;x_!Cv$JDQZ3wSfM$2pd_)a(MHB*+?-t1`^ zKJ2g7&u`{)MtN_8F55yEbwg_3Tu`Q*(j!(T_hwba~^1`(ksSG5JXs!cAK!qLueVieL>4b(rcB z%w9KI>^7Bu`>-5xx<#~nuS|(sX2;DK%4D=%SH6)F z+AZnEo>2#R7EYUWhJJ9qVdbd}9%YiHp~LnR=ESXHr3%tT1R?F6kaUhxcr#u_oxy+# z#1v&;kCmE^7I$`%+s5&Ypn+C->P$ms7i!;R!`sSjzt|(6qj68ibZPdb*H8(%oQadS zZkLUEAS4D|>?^ovCCtvbNzIM5oz`J4&PCgz!Apt$jCmWQ?#Irw$KI+f`oMm3^>jS1 z&q&i5Lf9Te%r4Q*4K1BcZ9j=TqZC@>g3i;%@nQ+*q+mO=%Ht9vuRA6+E#JU4k`MyD z`4FA)7+hImeW=X-%9Fm+!Un+;@e&DN*Hm1SljdFSx2xrwIoyWgdX`CL+K-3OO_&si zl2Cfm+YA&8-vDFlqZ?rj$~G1c|BP3@_W*vnv+Fah&lk~@JfkHRH>Lf*xAbJdmi~WH z_trsiJzcwK2nmFs!JXjl?iSqLA-KDH@Zdi9;1Jy1T?Tg_B)Gf7A^E-UcfMQoy|?O~ ze@@+hrlxB5o;}^Gd#z_Z{jA>bw2tyy;709TS*|8r+g8~x=etd3GwBO~pd-Xb|5$~& z8TuE5dkAU1-yossPoJ!1wN`g)Yn0Qy?l|Lt9%mAvh)U?Isw_j(pFoH4`)=I)&(>1k zflfcuj3!Sx8Sj74;}t3}Ug@NsVXaz88RovMLuHN2cmyK7Do9f1=QNaL&pss-rqwvO z*ee0sA&O-|K#T4rW-6u;l)K+$Y}EghF5VS%XcW69%hW-i!8x9&4GRsJq); zvBfCVuRgIJV9WEsDu0JkE>~O0!3Tf}@MLYc!ohpiWa=d0qWW&bw_Aj@A~D*bj8pMA zT3J%$wOEwBD3|KdliD?!iK_C%vly&)IJ>y}-lsIm`!?lZp)ri=aKF5D_+zoAOiR(} zn6d1w@-dEv24~BR()8GlopHcV6br!$sN&Y7w2QNnY)$DW+SQNm9ZHgJ+;YZWf013e zsj1vvDxLt1Rq{1yl{3v-W_5TUi1uGjEpC3W70Z(9ZSU`7dp%JV9l&e;`jRG8H&6ZC z=Q8!^okz9%6svi=R1t|^>?e=77C}Mc0blRdaWYnck@i?~Re#pUw+8SYLT5cE5HQ z?pf7Rv6xU9ed65`>5-K#FKGpR{IxX7rzjjSU%Yk8gn06HWR|f#DDH@EFdom^kwF_z zi|HF{#YqVdkC)2zPQc6*O3KOe<-D>Ab-3Gb^pj3SbH~r!N9AQ)>Bj4lhi0{!gxg97 zs>(4>i}mz2DwgBp>#8O8wiXKoa)HEiJEaq2s-kA=hmw?zwGq2zXj%t)DN5Q)-rJYv zY&P#T5!Mn7!TZJ*N<^@LO2x4NtEcNvk8GS|+$Zfs*YZ*_+|haysGl0<$=sS?HP#45 z_)C`Cmnk-A)>+>9e-UWf6z)U4ujH>XbeTV-8B_R0$dd=FdF`^wruM}ump~= z%Zw@-_pPLPm71*J)k{ z{G71!nXakIwiBPR{fx2(fvYm;ttdpja%NdA3gPXs;gpEjQeuZUnJ{)wg*|KI(5(tJlugt{;^28OmGfj3*n4U(?9^d~Xb<4vapFy0`z zQ{LG(ep&wSApCs&nGp0Fr5>?D<~t|-ijK}Xg<1;Nsf%&<(sUqB^S87I>4oYbS}oPd z>D}$>&l(FCi~cOflMz9L!7$gHNvcehkLva9#ZRvYp$&_?mhWgSp%v#qDBvGUwMTrR zID5&Fxem@m>KfC))kijbodl{BM+y%X_A@$@c%E%wIIPFyWtuuRB3=&z z_^EfTr9Y)7c47Ozg&5@@Lp~`PKUtLMD75nMoSm0+bGMl?{Bpq!VLSSI{Pu>Z-M!5& zmI_^T`U-~+b-2UTDA5Z0mlJXQ0@4?#J+rfDrecxA5V=VcK1ctd?phcO4RZq3dxP1sFffV2ZMx+?swyxwOO zOEKkA=#FMu{X@1%FzB0M>O5sIweQhJ z<7~(XP#0PYZMaJ$K4r;}0)abeqWZf-@pO#`qmv<_M^uXF=w|(|Jx}d618K(a8L$7u z(wg=yHc#d4nytBvgrMoC>v9LdKziz{cu+sDIHztkbyzddK3Ml~WZ`A-?FWPhklNqU zqbQV`NeZ5)!`XSJG<;8b zMrh;puV55`1*ifVj%demTIu71FoaLV8=~6r{Ue#pe$nmLL2Uq*iUyyd&q=wu*8=a} z^AdBa-^2RwB1wS?(0^>Mt+xDXg;4%n`fbsx|6KyCc&6XSk84ySexE$08T;DBDJ2t! zb8vH!m2DJ>j*D!YKD?}~ga?G+PmHe$YVjO=xFokCtA6@aPqQyxRa@t^ZTcAU`t9tg zL>aFmscQ6+tYc?acm;+Z>a{XsPqeIRZh^%Ia)+7D3JC>V)VI`3Yrb3#FGIz3ZbfVS ztJ!jdTn+m{O6k#T|5ka;DdmE*z!SM>O%OfypG9M&E&jrVN`IS8LQ3Xm%vfBP*}n(G zA6gy*{xQcM#ecTQvIO+Vr;_--r)>CcGV;5A04*kbY27DyNd z)uAEb$<$_BUK2$e*}+(hlXqDzUSA@Gy)p)!^RJ90_b*}N;9u@3s3swBfi`*=6U-QY z>cUfP?(am&eIy7=KEMhw#PWje4z=b-iCaYr;{hp*9YKi6@$?rdQ%dIhqK-$qv$dG( zA1TKll-nI2sKia#GAOS)jtuw-1v&LuMDvgrzDyN**IscWxww5)Dx}-0TzUN` z-u_IdtB;pxH-S584pepYWbUjbux+6jvrX=LqR!uEYtp!|C8*1jFpMf$)~a-3o%V6a zOI5*Ci*MbNA6Ib{hl#dz*w%%s%AOQJ&Q7AU>+1IR&!5t3Vr37-)n@R22F#v0(h2#L zd^o}9mN3iVW_!&Al#~!esK)YK|b)y0oRX z@EOfBc*1RTR^m+*4Q@2CI_2}LC(z9{ynL4qyC|Sck(7XuanK^TRseV0mcBFHa_QrQ zu{3K}n598;TM-i1U<2(RW=2aBQKl!eaiJx6*Mip1Md^PC4XD~nCQa%eC;Adqwjykl?fOt)N-5@SEF)Zpp_YX}!91 zdmI8}TVRqgkW@>0RwaR)`aVphQl$woKrmq>Me;`uK`r;P%Sp=pZNuS!J5{a z18s=eVg$##%hxwJ1@u2jG*c#WRyNzGr#;RU>rhIU6wJSW86dGxrpgw3{FfsKzv0HV zFfE>cW4XKN;gja9e^%Bf!f$~8OXx5DN)FjD_h*!pq`OIAN*z3UKbC6@OPuHwPp0TU z(U?6{mNH*4^NEox<_63w?ymX+p#*`U+N&RrBr-3SWxVjq-%f1G#OV`a*&kDwp7s~= zF!xPPM>kk8pL{icYvs-t`48f?jf4B&K$wW#p$hN+1}e^@A&I^eQNgf~J=27 zcO2;G`D{Z%)`41GNlHgYmx4d4ju}@y9)IoRVBb~o2-Q7>R_&|&QJbr51Ew=xC&Il2 zUj_8IzM%pbzFwN}y@*ba_PI_(v;9Q7#UR7M??e~g&;V_nv^JJ7sWj*6+?78GM_1VH zbc~bQ*f?P>^G&n(5dL#!d{IENGiM2))-*(aGsGGanMQl6 zTY$>*W97j<_ekaqUteUr9^DL6GVQG4&((WQTK{;=N*fwy8X5|9imj8_7P8ChwTFlb zlWVUv7{o@06RJLb-M-R`4eeJ;L{JBo5QrB;z24;0HVwSC@Ol5RJ#ImBO)RUgs=;9A z2ePu&!8(LCir9L_KrO<8WZmVlo08`}736YTa2J}AQll$krBspvTCgpaY@$;|@^4C$ zb!?b(u!FrIy@wBq)K<13=1yAXg6I5O%{p=m4<$@7f*}Z?`!40>N> zGbvKzZ8o|I8&$=?8Q$*dV0o6Z2&+brQrI^4x`%C~LIcz5p^)vJT>7q?44pjR7o!^V z1iE`wy1zT=*TAsS8wsg~*hzr@B@^#K=e1yln@MXkR-l^?ugrRGGR=lSU!e!Ow^-Gf zqw@1sGO6CMqK9(JvuW$e;0Df=x`!Mumrs@yQZ6@ub4_IA5LVluISN5T!QJKMjqOX4 zFy{oweX#+i;-yTX=XeMD2tW8$4)di9wtkkB5XUToGqiWA@$XxmM&8WRi$gSROG3=S zkPYe0mOAa;^}g9dvsD1VmCpZ=0GJFINu6G;7PcxqVo|ER8j-TCUZqYInOrpGj7T= zyl=nxVpoJ>e){?xtxdaIk&scsG6zpf%IG|`QeiOKcd5v%ia#^ULCN|6qF_d(52H#>9I7k3i0J zIB|Xke^%AYF%u05?aK1`XtdORf-QZ|lJ7`=8oj+_-$L3L?|aUAJD5TT9b&%ii+m(I zhn&6eB#Pe4YP=A&#imQDQ0RX7MG!1KJ$>te_o$w>>3n&~kVJP+XeX)?^Vvy#vhyKe zsx1ORiCO7q7!j4QU_I*fSts$aLP8QpA$>2-w~%YCL~~v-!~tuyac1Mp7fKn4hcX zx$dR~;Q7=%YJa9~n`R9y+Ov&o(3vhM+n5tU*vEK2NYP2NF-0bgEuW{Jp-x0_?BpR) zUO+??xrn)I2D6entijd(2m&R(^o`s2nPmLG;aBZRo<5!Brh0OBuib`_cr%8r-H8{R zs7RPRMcS$^#ozzM|MhU$cR|(YZbk0CWS`@~TQ}u6-qH}3gr|Kd(UY+^SQJtsP`A1B z<6Bk@pppfaIxwB*PK();V)}kqVY2z$VV@c=X3*rQPY*Js+tA)0qdD9&*n5D9-RAJB zd}JS6cx29#q?FtTk~$afgbW3*9w8idFDocepe#$961`JWlHTkI4TBs})Qi|03xd@a z&m0>>AU72&)l;YoymN&$JXBL^d{RYvu?eb@4O@TkGp6A(k;rFk*YFxnVWo9!lHr5L z(wNIL_x6Mu!5Kjf@tL73XuJVQqdyzVSc9ju=$d{Tpz<@*1SdWb8wcDLn6+9#qAi|&A&L|?c@K1o@ym5ucmdw zy}S*6UG@7pf1q;hNz&oH8hT6iY_jep*kAkyIK05?jtVi61?IN6*mE5v{(gB7Da{TD zhoviu=1X$~?>gUA)Ll|L14kVJRfi5Eu*Y~lcUn;OP!1o39;YCzS?m(9KtUsTX6|Ea>PvG9=0-7@Z8@cOrH#IqXf|?SR>DOZ!rkp&Kl0P!!vll0H%*K< z)XG;#_fHXaWD3fw&s`jcGpc(wlkxh-SWUm(p=~NPkke*&Dd4%&(Qo-Quez*J{5Y8s z%25P{tRV`y){2|E`amRWM<^A-V8tvrXC)|Cs*@a|AGKmB&YtL5z3%l_Ro&4 z?fCRCmn{2JK9(yv_p%&6&;&<+p#E=j{1MgN!{lZ+{U=xG%lT1rDt;%dq8N00DxJF!={1h$7mdYr z7hp}BQ>zP1zoB~AiT&e!^zRiqu z-(NjlxpWz#@Vk&;o;}5yFrKbqbEyTNJJci_@UDUpZvWimNn*QZVh$0>b}yqD)Ku+lat2Y>xqzW zq)YM(`N*`;ky&Ef{^P?Q9Ut5;Lzc02}Bp?lOi-XR1xq;*!f z;v;{_?P+&&Upr=xgNT&Mzsl@)6DM(cRW$Kx3GLafaO9@Jzw{D0Kg1sKM>r|M>tA%9 zzOxvTmDP>z9UcnLit52!T7uQ$U7GlgY*XWSAl|O&`7?%tJM59`3#^g$^e(z%zl-iv ze-J69=YvmmTAfaMT;}hkF}38$UwokFq>J6gVMu9*cG(5JR4YL0@#aEai*r8d0pO2TB1zDfD+3uGyP?y9V;>Q`88bD zn=+@T4VPU_B3=`_zBB1{>9TY~S`k*Tg?SR|Gqtzo4QG__kI{vh;hRSf=F(QHfJ*1I zz0<_%dU)=^V&i2-OYYQI$ndT2){@$8pF&B)cI5GxM@FX!u*@i>40)iw^d$1%*sUB0 zo5)oJlzrY_&l!?FW<(9ikTTO?;GEspXv1u-LgF2e&NZ>uQoiCX*JUhFUxFwFxIkIy zm`^#f*mCdmgfYlgWON7mwN+BJCHkCjjh?T7*^<$p>-BLbqGU6#y861}8XuEFDF90t zaD0`x4=KmUb0`4AA## ze$KF{)?KTWTf5!TBj`BgEEmT%TH}5Lqq*<5m{I1lAsBR;f~U@s1RF0OzKI=gBB8;I zZ6%z{8q^MJg$g#foNG-9EJ0@V)U3}pu9^+5hwv^a2xKy5SIlbw{)E6&fEOx;L&hNt znOkGxFk=2IW zrbITp#iW^$Z!$LBb4>9KhnW)mPXbw{B+%5pT@}y<=8H4$N5JK)%4_m3x|N3^`5!Cl-bGfi$6 zmV#UeZ~NeoZ3b+1+#M{D=kG$R6uu4QzdVe8Vz$JgQS9g`%Ky7?dBBdPNdO=A>U=v& zt@SVWH5#ZEC7#6`o-cDk!NYE{o#```D2DNq~At3F1gdV*@Dx=uJbiX zCkoLI8R!m*nc=WN-l3tp_a8nq^F3;&jRjFbAoON%3~6{ak~mB!4`^wLs{6LV-W=F%-P*OgHJn#V0AheFGx!GP9>+a z1fHI$6mvI{j^us}{=zM1N#O*x3~;zWdttN5y1q*rO4&*m|7kcrSnTHRp3gJPeB=vb zcS**sM{<*7f6udv0;|~Wb$&FB$+UxEzN>b0me+ju_*lquz_hL29I5&6wrVCiFR2GS z%=cYou6{js!PH_uz{(1%G7yKq9e!*uH>2ml#Mb}w12PH(F`g(%OpIApE2|=f!0pO7 zv=im)=#$6B)7HLq?N3HTo3@Ju2l^6(c-|;`n&U6hkQ(3K$Sk)WlFV2d#e^JD#`F$5 z*y7zA6@0uoTa|+Atl?U#n&5KV=2#a4%zCsGQ{xGy;q0Cf2D}&XS<7hkx4}=TPg&SX*p{uBnC1Y+33v%1n8Tsh09@F*dOraBd1ou| z7bh9idAUY*TA+&N+gbW1X;(#Qy~F(av;B6}qOPZ=PR$*nBpg?D4yFMaBWZjw8u+k# zYA^Sv^lynh_M;TNXn!&$j^PzMoljUYTA%ppu-nd;PoXUm$RZSR`_iObzEIqAIYxUd z2ptCmy1SYHUQ!y6;tg8JA0Yh{PfFinRjkmGLrIP?ZlLEx=b2~t@7!uOB59(bzCUKQ+99K31onGOz6TIqGJ2Pn;@R6%%A5F*ONWBTmV01y{nIZB|(UcLd8!r7UoOR5moL(Hk$h1(CC4wS6t1IV4>z7Ym z8aB9~buA=WtH+d>Bb&xujVK2bL=KNlM%jEx1*+#&k+smub8MSHXLd!S+4 z+ndC(ajf1BX#NuWeC%I4!|VGs08n9QaXc_`JOh+~296)+8_pGW)E09^vS}&^ zvMktlX|MhAOKYbJFeM(N0Nucwf=SYHBDt0T30yTSj=l{&&%@xweKjo~Dt z6jkK;sGG1Pxozvkx0Er}g%9Aw3;7cDfw7flhw-kYT2;(}ZwEk&~g`63L-uP?P6k)VTnRY>! z=W;(oqdy?Sgz}D>XOITbO2+e-FxSv4{0-A&;#zCT?oOunOWo3lx+6;x0Vyb!VQq%y zRIOVgB;O_AOib)?A8dG}%2&5S$IwGIfgd8(M}DxSL^Y2VGuc_t<40-r{Q`m*h7JR@ zYR1jxk!TpZ3SU3B*WmSFNQq9dON&9=YQpKTh9N+;30XK?zlmHr+*-NivQF>6&^*?1 zQ`6#|o3TdS9yA%itmtt6=#Q7p%ZIm_O#a?_Z*&XQZ1z%ageq|!i>uBIM9Yr;IjIrf zP)VH_GGeN}B|YyyQk9Xh$6yH|M>b;A_sd4w2REdu4$mF-QL(JiC#ii%=}?NT^ulrj1x%sPkDyktEdYLU3(fU86>7>AuX6p03tl~in~XmZe2T3%|IJAtx;bui+5|%L7!_9We7HUlG&vQ1x8?!VdhC&?8mKcRSx)LW9dcD|Ojg9MZ4|_HDr%@D( zzGWGDMVD5O>udW%8t@|vq&{?e3W$W9FZh2#voKl$&L3d9?WOBQ%!JT`G#q*eq6G^Uc@ z$|#S}a!lQu`S2fPFxwvHq>wGq#+HL6aL+KyA@gLF=@=*Fa6V&0 z1$-#=R{9`-%GK2npT=M}U>yCud9(?Qm&);(=8^_dM>wMnnZragmf;Sq>3(`BoRtbJ z1~eof+dcx~wbkdfurIf3I!0 zPt2s|cI0R3S|IkK4w!g{nMqGXhKJLmqu#T-w0n8}O}>c8ieAM(Ysii7^eosah3ICE zWu*%f-HzcIfw$CY6sNScC&Gc?-YK^|%;5VS@|!i*G2EzLx^eIqkN5h7>~8Cena;$P z+2Jr-2Y#f|STB6020li8(4=r`39-DgGOflPWE8KYk)CANApz z;z{-;%+@u1{ULy&awNcTs379y1f^JgZ*pvd8*|O(eaD)+__qjz$IWf`2Cz633ti%r zFZLE;?8LKF%K0%FLT1OAcKV2`M+HJ1;UdIlVs@f}xou zz16juaDYVP=vWc)y~Q!1(B=`v$>BGe7<-vatr4tccL^K}K;F^*_RXGhu>O=W7U=P<#P-lA#SF_(;pn~@QK>;^=jNG#2&L5S z!djr=i?G}HG#zbWFLTWv=MF+y`)dmR$#@cei{aNq4}zRy#79iko5e#66PBEFBG(AwR2LC-7fT{<-uA2r zKHK5_(8q`0|ke|?<5bF{r%~fP$sbsfC z#6`y-gw^qZf!?E}H1VEywa zGIh!?xO%MdoB^VSp5Ni|r1A>58q64ST>&I!Q#{y!I-$_E@oNU*VWafUVsq2Z;gmT+ zg6aNpik)u`Hit9?P>B7ln|#4d7RpJxT`vkwnDqq0nKoBg%&6pA%*goK#KqDcdL+nP z4LCY;SX1`DzxnfgzTEpQ$@lsTL#_;=GRs&{ui9s9a!3beNnc|9Y(JvJtW!^H{<2Tu zn8ji^-H{uEssJr+XL0Nyry8s>*dlk$ig4-ZJ1!~Gp#{Z2n+Ik8?fe)zLp@zs)_tD0 zW-xbpkJk)#=UKHz+ZhSwpkEzVzvFrMzWcP=Ns(B3%f``Z%Zu;84iM7f!mzN2^m8|4 zyW~?1)j-!584Y`S1Qc$$)Eg|&f~-w4M8Qj$xm8nI0Kd+6E0u=YR$GVl5NdT6T)|a; zI3mA=x;0wiLZUkN*Be2E!VlAs@8C~JO|~x7-sR%MZ$54|N5JTy{jUG( zuJsAtu##A0N1Qv#-#cz40 zpgdE=CWk#(0Iqnyv3%Q1NvuVEzLNV@o-%9d?Cr|}f@3)4TIL^u?oXOY<99WZbtFE^%*k8i1WY3;hdU)J!@23ki!;36 z<}t_DSlcM$zZ5>UslV7xINnhfNuJDZ>}Lf+xxSD zx3}7d`2}G3Qi^S}k30qH2!h$o&4p6y4^iJAe4hAM25e1KP06cG(kxS)_mm{vCkN^4 zkbGWFC?Kpgm@F}P0Us5U!S~lmrU}K9VQXz(Mc)%U>z-@wjkNU?B?*ii@RS&e#7fA5 z7Tfp(sfkzZ-hWyKt%a+y;3;Ijk0NeLLy`?Ecz>f&006L220&mmWqit`pJrJnZh25{ z0qtS4C#zhEdDDOSrWO73GH|rb9xgj5TdO1eH6{u>)2x6hbZY^;UOVn zOC?Z-zLNf04UN~X!!K0jI-80PFcvcuX^z<5fcw*0M?UU1lh2l@$$7FG&ta7XSv)A~ zZOM;6s+C_WPM;R<$=_}%?Xx^UAMr1A`dj%A@_?RPv6{E^#sRy9<7eao-MmCVApok)HF^sA{_=w*NcRP&vaE6;KHP$*0-izuXq797_-!1s%QCLx4yMuHZaA;lq3E;RSFh{=uNx+G?1qoOLP1qKoB>H z4jla8F0(!PU12PG?DTQ63WWa9fHl6C@4U$0twJq9m-#Eq-=A!rgzFr}WVe?r8G|k= zql}eoiM;=9)tp}Jjl6?|6qj{BKN9a@qD^Tpq?Q;4>t;9#x;j8kG+ zZx+iFqJiXR=_JVU4idQyOeN-~Z?egC_io44gIE-_fZ4KngSiHA-=2K&4u`U&RG;w}&lKhP)gn$E&d#a(E*b}|u}Wsdyp3u5 z#iu~>6{4W2gfWMFWEp4CMb;dlULM))+sJu$8&K+niAquGD$(CiNyK_oT`IPv*&kT0 z+e=^NUYgxE(N1`svC)5hm_m?ktM5lD=k~;rrf23@A%wOCjmzzE-*?(Tsdl%$04ZJU z7JHq}Cx`CB!KspGHAEwrw`9GqjPFP@;@Smxm}%3@ z@27h|b8kwls%oLfP`}<5E3^9a((^wHIz*XYshF@HTS*PYVQ+SK4?ak7(kn6cI~?CC z2Z215T3n86li0Uw<$`Cg%$wEl-V6YC{A+;kp^1r5IXMn5O32YnhxLqw=P2z* z({Hbn)g7aqXL_|}=XAfo7abc@e#h|Zr=feJ*Apfd|89}?&V@=Po!(2|l|Heahf{fP z*)h(i#TsA+V##z(ONcBY+B60%D0t>K2{+K})neN`l2L{CL^1g zoW8{^3Ef1bVQB+%)dvw+b>u2TPXP%xiRYD}+mG}n^3?eVBJn4Vq>~x#5asfC#dayQ^r}iZd7EN6xpmI4x8WPbKpyO@O^^%-P>aFuH5L z0Yc{9?Ao}#ZQSkJl2qBB3%!;R3=TRylMGgT;|pei}H*JrN>FN=r!Q zUlIoGo!TN0TOWa69o2acxsSgbZ3C}OsgFD!koE=Ddx=gMfJ%I!0AbkHVaA=N0k+(nWDfmoTZBGlia;aau zR|GGuQ_?jrxUu9arT%0p!Byc+T?UW0ztn;c8W<;ZlQ8!`OmTERL}goZ)|Zz9OD{IM zoCb|w3`GEwBP2TCbN%u*t87oX(l@ADtcCFn?@E*8vkgU4?)~G#H4%N$iC{pq{Lt=f z#r0COvTA0OvSMvb<9Ys6b6nxY#zqp8+@jg(BPQ!IJH{c|eC z?5Ymx?E!oO!gQ3;%;C)ig5(D$@Y2&*ZHiMgAG9fh^Ht$1Rh2MryVG0( z%O&VxAgXe)ovXA`w@}#tXVE%LbP^S$d{yw*cob#n@Cs^gt`)U2-nvRvhx$6Vnux{E>(1Gu-LI}`(sZg< z{Cl?W0Vm{P5v-y(dupCDMg@XykhgGaq_A20Gc*W=i5fDiHH)rs7L zH8wPeO;J^~)~rXrTBp0!&K+*$Em)58rC&Gd%gF9V>8>D9z5{APRw?lhL!#FX*t4%$D_qC+5; zLQ*$=`47#-hNwQ>7fspccsDBbc1`H#{MP|fwtvfukrrbG{;RZ1*2tpdFI)8Xkuiw& zU!`bV|Gf=K4C?Eb`9*$LCXmnA`R;CChoaB5CrgOw?Z)wb$aT7{%jTz5rbw8>G1SgK zi2M`b%uuEP6J7c=G@=6?lb!>;n}XKf_U}NY2g^O+yiSht>l=-zmb6e~LN-pxCEX9>eA^QGLpEo;*l)k2*zA#IPx)y3! z)33ByOs$u_NUu3WEnIyVa^fyT5SpaI-VXMRIztQgf!?@XDZ@T zC+t^S^Y#2|{DaI^qlhONt~dEZRq{eztXhZE}vM@x47i`KRR?}Gnh)lUWT}Sb#)0c{(ORhaqp;^>%Q+? z0GjKFEg+V2pp@;V$+WsC>T1I=hj}XiSIONt(z2A8(xms0x0h{Dk7WcnP|59OK~~J^ zz8R<&r16Znz$VR^FRbt%_V27mt@>L z1+PC9nSEYvjmjbag7t!dxGGj@Ekw*>AFIt@AFQ#}?8s+>f#U&~2N%GVR=L(g;Tu{4 z9INB-cB>nFaL=LnvU^rgMQt7v$O;M)D7=^y7XPgUDC)@hIlrz+;oG}3WicrKzPPYW zO=^5up0hvDXW_$`v0i=}Cb#?dyJpx+&E%=g%hQE5wd|b2ZA&U)7KMN}mQopac@f9n zPL{}Lbs(_8R$yw)F#*1EPI4JFgn#FI{&iG;ffe9sCT}R?yamVJ@*K{19*Is?yx?kx zzKV^xXMxYL`S+?}6AH5U;!abY4%;@RreNX4AEMBkTGtWAmBaWIQj%5@}LVWT$R z8GD+J*c|Sa+XwiYYdU4B1S{y>FWFU-rNs<`I9s(je(7gRjVu(<6hPfsb7;TlTIp3D z=-@GG#HZ!`Ln@kOp_w&*yx0D!)F6P2w`8xzD!m~%M#CwR=V~2A}8!utQN^Jjx@`{pBsZK z8#GTZRP4m~U*fGKC<~XIynJ~Ao+Mir<}Rs^(m4YX!I|4TJ(vtNUFAYre7gL>Tm@XU zm(!(TxME_|j~e!;LI$oy^slTzzOET!tu7KzhbsF?H23&%q9j*clWf2QQEK%8-M%_X zXR4(zH(aLD$(BlJ)eI*KTOB94xTKhJhg4-5JI<-RhJnc*^S3KAvt>`u?ViTHR+nXs z^ObK}sHpztLYa@Ax)%qxzi6+D`GC3mzU-NxM3FMRG4k=|j-&lhtk!8?UeV{dY8!Qx zks7%hraEVS&TtdHtwy|$Y*Bn~gY7>R z5o<<8enu<$H@Fd9?sgO5UoS`j9NbBwi%rqT^Y8aG8C#+UhT-nH0)ZH6la70GEZVhE ze6mGSaHW#@`4XGa?yLLto175D1q>q+8{IeV(uXUFgVrrr8?oriqWGrMOGwhqJ|lta z)eD-K@n zY{SqQGA@J=LldRkuy}CgLd(DC(@oaO#}XTziH~3ut}m;$zkseKYG_ z$!aSD3~G@F7d#96VF*1phI`YIe1^fBMwX)80hZqqB~HV?3GKS%g1CgaqSD1<#Psnq zw1;*Ht~ZNjA<%1S_N3;<=!YVq*&24GSt`XMr%$-t7Fst2n;dZR@~1^obo~KpVv~TD$-E;BCr{DsesAvG}}= zj+qV!h_S0$fJ(nBeokQ-!PwtiOB|(96S2}&x}Ee3wKj?KIb-tO3%s_t*=m@vqs!UJ zuxxAcgi)YW{MZL0ZL^|S;UUyGAn)(tY|EUzkIcZ}dIIm;6Ft@i7FV|$h+g8QjrU4b45H|O_-IVXkq(pG6p7uS+br_#iAI@Z4FlR!cOL&hj#|PG| zMLduNE&wtpCoa1NTGX5G=~P7VzI%Xci>zO;Wy|@7gfoUqcU^ppsX&k<0|Q8DF+KwT z2r_g)a)BPF!gS%opCtUB9FV;-Cm|Q@EZKnbL(stikk0Rr+kgM@{MC-2BSj-4V=&da z+;}egB;l60(p=}=0f*RyW575JC$89|iwOq@M`zoo69|!8AMt5M1?f9y6kZ~tpA!k$ z(jKJ+RlQ@3&&X*ibk;Gi$UQw>mG8+k=mv3;C?P2^BpP9rC4F7F^+iQBYe>8*JMLjg znPfOs;zi{~u;mYOAJQ{f5|eeM9K>rQ86s~mB&>^K8smu`o^1XADz`{Q{wYO8@h;4o zq;VT2X2)z$>1wU}DW+$=&**{RsJgl{2`t8KkJRx~25fd98G2zr#br36S0swFmz72> zbpost(eh;{0SDN#PF-tb^<1fdN6AgN8qx$?#n4XY|h)r=st+A>iTH(nQxg ztFA6yQ&LAYFIB&rE^#90B2_4pI#e-6h2#xVuAecQ5WPfnX=?-uvCpv(A2>4`-eC!&&Qm$s(+f zW}Nh&?ZoHD`@;d>2}Xzrp6WNw%E+~w_qWP|4o{}j%ZLs39}I9C8O=2!l6IN5w07D{ z-v*+5Hd59y&B$=rdF`+J5JE3ee6bq+F7XHPZ=LIQ#dN~9h6>0lRyT*QTT2w8FY@$T zfDVVa`0-#KfQuRnMDn1gkdIQxrst0K@~3UOF1$60YAGG$kHp07oHSZ9!{J0jB5PJL z-nonoskH@bjlHC=d4uDl*N()VwuZp~HmUIF4&LH&v10EnTwkP~-}z7XZIpIm@2^rg z%vG6Fy8~V5)CHmOj46~qRhk(y28|;FdEXL+p^sca@ZW(4+L4TR?6!ZqlPTWxFy#9< zQ#+?FFQ&Xn=fjb>LqBD25Mx}{)j2QOm$Eb_88ZAo!=iv5x%moHtp|VXI`fZRd%uz? zDdBjFlW;kPSXAWAs=umVAHf>|JTnE7$_n2&*)=9=>X)oIGLyhYoz^>)xV=Ah@eezo zcCWncaV1XAaBViP{)*3G@iVYfD)VIgX(d@(;BM#TwAGJoB2Rx4Gq51FB7-F_#tpdr zB>PXuiO6J|9r6E|HwokowXup!%2EMg;m@; z_DJ-=(~9b}4x0i1(maX1;@9!{CXvi!V@lOt9_n;*td|rg8OW3$8{cM9RteNl)w_Wa znVGTMmRf&sk_w(NnMPvyXTH|;_tpVq7{)DOvpLY|t|8~R|H4R+*7_p*QXK~a%HbaH z(Uw5age2e+X;QF`Y=y8vxr-tbiJP2evIg%d?{YOROgz5aUUS8ueK&m=?eD&z7gHume*!rx0Reu-7SDJExEOnBchbdnQcLCDq&=ZBkJLvmQgE*p7i~fQIv?&=`l>e4=Zg|AUpd+kn%o zt#ZPNRw{JCKz7~vvfK4yqeXP+oALrSe!fw{lT1sxo`Di1)*hr$qnlu>BTl3uy zv?~pcj>R9TWCYB=Jx}I|Kopba-Gp?qf$IjHbU)0w)fLQTZwsU~MWbOOv0O^Q2jK?N zDuYn22M=yk+vRe`7AlKS!rJBs^Hca6E_slrvTu0mAEWEjnhioelzpG8nD9cswC&xXRmEX7!1?D(59nW($l>@U)?v9)}_91Xwz^6fDlKMZ@OAk|)@5BbIoznhv z-eg!0V_0_J`j*QT|shv~}ye1^eY+ zvLosSqe1_Cl7)@@Z%rdn`2Q_D`v1hsr=_I^o*t2eoeG8|GldxDH~w+K=S_QWCBc|Y zri9qD83XN;CO>!1j)YnSA06OqWPY{?P6GgRq%{Lgw6d31dq<$9Kj&MbiO~A6 z;~r8&|Dh-(3OD&7I(+@=Cc#DsGt)nB;O*G;n%~ZF>7D=poRKX5=9JJy3rZ~w7cbkA z-QXZkCeYv5Kd1XvS7&4YK~q5J8ZRm+f_z7FajCxEv06`pRM!U`-*__jgIQ2{b^1U4 zzwg3j15A`}#~27!GR$gQ9Uvg7@AXi2&mmqTef5164^qDb?Fj|sH_a00*cfm6*}s4} z!S$&~Wuv1Fs+T!Z%}=r`UXcYaD6rTj-xRz8VyC*!9LiX1Z*~5BL?*D`&tWDlIgE^p zo02I{#g1xovVE>ACPF5?rM*YlxPRSR^f@MS8}RJCDPz|HAsN8%|E*SSC%P&Lk7CGW zLM`u{77ZXy@i9ZIUz+``gX!$YY%~T$Tit$|INI$J0;+wVBJ?L6RMGrvWamL_($5Og zw{G9y`<$k$kiQG-eBxX(InfswWmec=_06Ch{x0`h=SHvr&qun(V;$B4Sw#XcCSDlidy$am=@e|;5aM^so-co&wjPs#1U zVI;t6iUhDa-o>ZZ+c?i(nBDKfiSy?NIE2pu-mXv*#`2Cd`$IcDuErfJvZd8WlQD_ihGh=k7TpR`MJw0Z;PTLr&Yj6{8v#CnqUUHSzTC02Lo;F(Jp z0&D~y&Zj2l)`|g8@LKz)f|f#fk+}R8N^{nGU*I6pr<1iq2`<65=dLW?D}jG+n+JK` zt60b21!tC5yPcnz;gizi|1&;#frXs06v1?ey3gbE@iS?lr;8IM(0@9tKn+P_eDc*- zq1v|EjJq|C7#c!VNGYn*EILL* z-h>pJlB`%5J$q!h1m?I&@9FApkDag6v^tBqAPV;L3^IQP$G=#CZs4Rq>+>tO?a-fG z0xDHkCRbYr0?5h?CLCV6%6-xWf-HCc1AWrwsGv(w%6ATX#qMP;^^IyI$54|#e*Rtu zgFSaxCdue|qbJL{r>7$eo4cc3`iTp7c3zewxOWy3BIo}r@-w5f?cGaf&Ps9dw<&aY ze z6c5?TmL2azka#t;?Zs`Ag!A$#yPp8ALSJ3}+uC46dM1Ef2NN!uqXPee|L~ko?^?T< z+=u-IkuqZFH%g>fM*wi}R|nQchmE7inOdM5F#%(R$QsAvT41%?ju0o0{orcO3rTYnL`< zR8{^v$Qc-%@u@0SH-(NsUv82QOrIvKeWv&%Fvew1Eq`BBLi@Zq@r`_b)cO}-xIpJV zl*srC3yJR!BR6K|x#Tp29sJ~WqnOKRVlok%FVKwMSIHV0MpiVIEX5=JjKQ9e$q#Ne zPpJ{7^+t)RXsZW{87CR3>uM^Qg@4+mUdIqh=I2I#V>QzptGiJC_ko}1ncqAP%Y!S6 zi4LhnGbBErH>0-)xp4$3KC`YhUR#U7Gbd0^(*A z?@t|ZrPXGrPK^I4_(3O@VSWY{EtGKDBf906>JwBo@2QuFyMMDT9EK*)P}m%G)}FVU zdHw>honP`h=gdkGHz%kj00tYG8&`c^o=42B3pBdb8IShQ;+8P5(&Ywc?`}+A`vQs%o5@1o(z3 zWM%SCi)7l#*Q$Kym&0oQXrU%X>@gW0G%@ zV6{83RMqdYyZp>{=k%*R?-iD7K;rkLjD^v<%6~k^&*$Xnvwc6!soCRY-ooxy3H`HBjdMdPVOE>-!9&@DgySVD_F1H^&KR1M*cJB!Z{?u;D zgb6)-m-+_@=mmV*l_*mwk)*71f?nAc<_C9-`~@400-x(pOSQaN&Aa`+ETv&ORkPXG z&+>83sOuM+ZplQACd{{3B77(tTez#>>s%4k)1d`S6*N^;bi`5%_?1L&PMM&hY;LW6 zXN6Ve2-zUEBIL7gr*YZ%)w|@I#1wE<>`1a*d$)^3WS47Iq>G+8^iT_g$jJ){mbKtb13wbYv32O)=S%*l^ZA1C<2B`tJ$vDZQxF63V4vCP#I#UM z8a21$tNwDb0h*;9M$o9%=rHZhgK7?vExWCxf^W=+pyniIXU%)Y?_KliRYMK+6SdyV zK8XruS3M@O@VMij@7n}9eL-*T2R-I{E*|mN3lo%sv!x)7v3v4sFVx?sY!3A~PnVFc zCP)K@1wy>dzl2CK{V+#PV<5;dzAIGFz(il8Qd=KSerQsdL?I_4e~&5cp8pW(n4AI+ zwC#RUA^Aw^X>@nPG3dsSY0Wfd7O~-I#6O6bKzY8*J6RrRXd0#2_)&}Y^x}O5NMcQjH?RpY8x zjca7NGow!`8Mn5t@i@3;WjQd(jK#{vl5bn)TQ_`)pbJXonmkyYUa8kTpJiu^mEqCj zt@fLR^{1Nx7c>V|O*8sb-@3^CY9Zsh4amaNFfkGafA|Z8?>KXM9$}`t=2I+>_Di2$Au{Dc%w(YGHeYtkTM7>WGqML*0_H4H zRQ5(j<67aN^P7`T5q`$Z> zdq=qCdbgjTVIb&=a3b z4;hm0|CbUlHP*k?e?wmWFF|3KSVBpmC3!Mx>K3Gv_sZ7m6Cs?A2i1Y*DQcdB+`h%i zja5Xt?M-o%&AT?Ir#}Egh&nRd2e#KLeDj9J&dfr0%z^^p*XYrW5pgQX`$ zv0eUV1A8YN#m9lo>T-lFGr$4uQOq|L{-Sl)Zca{J1FkQHbGI?uvW>Yc7pp(&c#G8E z8Q#2I1!?18M1iKQ`G4>t*~P@S=@bY(VuT9!{|Kr*b8_w+sAc0kz|xJ4|5q&lXU^34 z!ci+$8f^-+GPQ4BvaLHyjC_B7J_$s6qH=C2+ZQm!>!Cm&aaGixNE%ga1QI|t;9< zi#vB&%d-u48I|jOf2Bd}lV!i~p=dOp#o_pQZ@dLaFRJ)eOXT{hSPe~nhDNW(h_Q{1 zrcm-P<_$|uFD&EYcjQk`XrsW;{0ccQlzl)9_9Kb5ijV82AhEoeNY+9Pag17KegXa?4%0Ohpr>w!lG(Ku#0rhCTk`FO0kwwZ2e& zGu1^LZb;Yf6EU=18u|M%Bglk@Rt@WnbOJ# zeh8qxAD;`S#yE*fE>|6}_na0yVev)>|6XR?>y-ish44FukH2G1<)kmVwxflI2Sq#G z;8RcSMidhy<%>|vHf-uh2V`n1~DO@1VZ zA0`vkdFLnaha|2FfN#+VBK42B%*tEQmrbXRwqiQHPbzK(*| zQtGB~MVWn+O$j{H%WrJ^d?Ym*MRy>j=Zf+7M47Hj>^vYU3~Sbq2sMJ005W!_W8Gho2>Q-ExWK5Cqt~MC#f-Kc_p03pJYDNJ>^M_oG(0iWHxTS z$C@!o2#0ra(~+~>v5-p+cBMqil5AZIjo(61p1OtptdZB8(r#OEhN=y>+YD_BtWmvL zq~Z8$T$eNbs3=lnqz>!Nv<$q_;W9a1TI%HMuzTr4yH$JqRyOL3wr0cib}I9XKtm)DKSiN9gVH{wAwGpVhjdR60my>hAmtmcJS82k45;DwA{E%=grZmNAqsQ%q1vU?}naWkYHok4l>O z%DLiqs-ce7iVDV6xawPxB(}fWeUgN9SDB*E%ED|gt*H5%`{WbTs?2>j;Ye`z3=V|O zuZG0?^%Ci&2G&+08}9llI=4nLtj`tm*S>b(oQ}x00zl|8bXv_?PIW zwC2hE+w|PO_QNy@rOsJJCHms-WTyQm%>({y>Ifq0(Cf0H4UEmb)u9XYIXH=2g_^nD0p>`_)Dm&F-i2-mtEvU@RS; zTXM{=STIRt@M~j{Ulj-m^hv#hENp`(KX%DP#a1f+YX^bpt<|vA-EYgcgOD|A;izY( z`Eu~?tu^iu>3vq| zpi0bvHiGH~wNc;!Q(lE+$i_}(Z3&;T__36#a{EXO%nx|v*KdMC9Sc83G;JCx@r4tN zCf1u{P4kBQ&N?pAk^R8I&|G){y!_+uzqF9r@0goLMxj;tH@z>bgbKA(JA5F2boLW9+7ZIhOu5G(FZ2SKjn4P* zA9`PGs12lp4+;d-IJq`hSfze%L1{}G9f9VA^fp6Ho5FsajUG7?lXy!K#%bH9NxF)t_LIuZQmH@R)Q&EHobD zZy5aNA;EuMR_9;O%G3T!%=__APU|1>^8XMbBhp2VGfpK}oWQ2w_+~shQ&%7D7TiVG zdpK{aw-0IHY?$6P>i=9Ga1?>0{#V^d% z)ajG@BF;~oT@Bb%mg~CTM?TWnf8y5`2P1 z!o*<*a%NS*<)V$LCYdp?H%=nuE+tAgyu>{TlH&VnjPvlo_0u24WyK)N(Z=lu9 zzxEpS5Fb<0&gMvkEDq#X#^iB2W~-8T+DQxEKt9kNerr9wXwHYjAZ>|M{yZ2V?D@nI zdmFZnh&xS$Gw$|uUdBKsJC1i=Lns(hsTzvu|NLrKK@2dz5+tSW9Dy>2=`v-Ci&>J-lpq3+tVSyraZR`taFTePPW`EEEZkKSyM`8d3ygo1QE z!O#w<>iJn=kbmDqiQv6x@dslbwtD6actXWuEmN2qvRF3c(I;*NHdZDSJUq?d^Fh`7 zV4K+3fvd&2oBz%;VP1C~it;}zgb|Wq6~de#wF0|pbX4NGG{V4|DN1Zd7o1IKU4f>q zJzcZ6a;x2o$M&XV-sZS!i=EBD=4L#G}In{-?dlwKwUoBv6LqtQYTsW}C*tl{dKdqM)vx+RjezmgBU$rqvMEgHQ(Yot~6{ zt%*lSgld9s>KrTJ*ilao2#WO%xXTTan4BNJ%8+iquI1VcH+8aA{P(t1f+4mlS_M)O-UsMx+6E#0*RFHh zkG|LUME(E!w)nazLg;}zLGg22B`9A(m6%s@ie=~e)k7xz$-Ns-^ z1hrTnW3~l>HflVQfcE2u`esC<-)@=8dX{sjWDh;vh)d(BT0gquL-DlpN#Mup2%UN`tpW2?S>b1u&q#QrNsf^V@ZDXV%&KS!$t&Ku3pfNt_b zm9;pn>=y}Zr4?(nqz`vt?(;_3aJ$JTj&E5uo7 z*IDQ=9U{27YPUqRNiPhjp)i;VslxM>+Qy6#JdC+kKcl<&kU||t=xFN3P#2e|nDv>~ zMTx(0#St@VtZ!qQBh0T5)-6X&T0FZG-&S3O@AbXO}ehM%96F1ksOq+;OrMaceSNjr-4$~XNE7W7FEzx z?)J@LkRkHDm9tWA^8|j#ACwP*p z-nB=y;3T;VDfxLPr^E24wL9mg?o9eGr}U@mgvr5P?YmnCu7y44vZ08!NNtsYkHD?V z!J~YpQ1$lH1dH0GGMdJ@to*0ADWAd*z;y5^5x2bRFk@;YyE8}xXEXc#uAZu~=7^i= z2j)Iut2*84H-ks@y78|7&;3JPP{$4OZ3QhF3}7x@WzHbobFXTnubNVbo){oRI9X+4>UT^hwMJ9uyhiib_n-!%Ku1|ON!N2hASM*ABz7*&zMSIFY61*eW z^JZcdreAHR(hlB*bP^=q%QvDAZX@_nLR~TdqstM0nIxwtXj)c%2u@>j2>>IQ{D0o)|xS@j54Vo7Nnj`(y=au;Y$B zT~2;$fV#~?&MSt2lafGsWsB;!-y>TRAatrsjo{HPM`$~5<&Bsx_;l1GDwnsHUC-m0 z=f^KknR%Z*;(8g{^d|=(ROwN8HCFgOOS<9}ge1F)tbeKn5j#UrJyybK;=q;Oq4Rs~+jbQe@CiC6C z1V;dHoMjA2%y%Hm#bcEvr<=f|x<_E4Bqpkqs1%RN zACWMqWY;@a0TiNn274AFR&4kZ5(u6Rgj5-EL}s%n=iK&iWzeDBFDwdJ#1)t2Wqe`k z2vP_l;izb={XQ@AXlVAvErqJsU^z`?WuPu5F_8>|IR3WF%%{w`gf$n9#8QPMjKit( zmC`F~!I(<@`zrg~`)aoDSy{8%5pt}Yfw@~-u<_tD@A6jCBd#F*@zedd=3jq^Vo6fb z@)r0^9q@c${ZJrEXki;AP~)BD;TC0*2vcsJf~gQRDkWiJVRLoHu4+vYA>AeGjjcqO(VvXzk9A|Lg(}AQ3z8kclA^Yd zrp@mm*oxG5qe}q?4tjQ1xV$lY+>=?;cd!1FH}$WG?R6EuoT-9SsT{Nt)Kr{0u>v=P zct0y_s+>CO${$WnISyH~u&}&j2MmAm-+aUPNWFWko}nP0(h{)A0cjM|?@J-<6Kfl5 z^;~H;PsHdGUa-9vR;o0@kC8m+WE7}64L>Xh(Xq!9T`m-*QQFXSaE-eUuy?!+C8NZ% z=>(6EX8Y~96-P;W9mC^)g`=TOA$KHMY=aw%SCB=e#m`}%jTfP610c6u;grg;wVrAw zfWQ!5zoWshG?|2AVnIK$IFM@p@@chd+!c;P zjX+TE`cYf!kM>dQ0D)q_$`)LwIoY~S7Ca8yQvz)$?N@V75P|U4p8pfYJ+m`0jnWqh z9}Aks=PzFo6LCN35IZj9S9UFGA+fSenD>Qf_?BCPGt&ySj_&q}OhpaJTF%1f%qDV<$hMql!R z$4Iba%$jp~{-d}2Qr_t%S=HK&|3`kL@NjI8mUaGcKKHH?ExaSSdRVn7ytusl7<7SP zBNA*l>&+KQLS($Iix?(e%Q7%`T;DmahNPtAnZ_?Dz_N+%*k@O$C;I4qBDTn$zoy0K z%lShI+--Rba`5akQv0En`<#9=4;txfWJl00H|dYf^05*M&mWcgk%79wp{TWXIGHBt zmnxWaX!=;Vzw^V?3e~W0?sCM^`9QuX6*uD0ip|H$`vxQV^IT@u75Y_Y-VdA}aR<5c zYz201*WxPsq4^I)uXQ-So;dpM&(3FTP$@K$;Rg6l>D?y0JX&JSf7asj;r!``x~&Uj z)1b)QMn5wOPrFq6WIPNmH9CU#UWGVp%iN{p-yVD!eb&(VG6hMt?#yIWD0j~kc?EQ7KJm!_dhQcG3=bfO+Wlv1fD(W;Cx2VpW_2*h&HT%)qp^*S8Wf-1;gW(D~;5yE%? z&xoVl#5A|l;_18J4T>UDPl5%!{W=Hxu{F{?6PHe^>#OR4eGJYEaCRN7(&wp0b?4WKd8NOlSfoN0cA?Pl z%EEoLsrHXPas)@A0$Y|ws-f;6kllHeuOqA!W}KF!(ej+OZW~LA=Yi8P`Z$SzkZF~z z%X>?_tv0i(rWVM;pe(@(sV5-f2+>Q;v@?x(u0b14<&Q`h5;D2Rek1M4Yt&OM^XaOI z$v330hax6!P2T)iAL5Z+4bBRu_QRfIIatBVSvompp2t*4$kVqYw-`mYlb|$K>q(Ke z70!2N=bqLrHTik}yVj;EV=Vg@n(WG5GzGw0I{rLAN}+t$vPIlyDAB0;b|bAZT#qC; zAVE-U#n(dfsn>vR(hqk{Ah~PTx`1V`_$BJFE$xY367IGhi9>>La%aU5w&wHs3$7IY zjR{M-j7R!b7occw<`jXuA+f$cU$kw0q=(y6dKuga-Dw`o=h_pRdok(`#1Y8M^Arg? zlTsR4SblE>s1hc6UQ2wNo>sWIE-80QY6I0VRzdQwRbj?d&I^!BJK!Y?{`BeCwp&s_Fy5tFn4i8<31EhS>GMb^qnkciu1!qLa>nfU;7YaR zX$N`!?$nX(Nj@m_NwE`TV%Ua0&gV@4X!WF}=b?TYSO{mt18 zHkths`uJ08?nY&+k%pSTr2{Y|*&{6O_k30HPdfS2pWxpm^A;ZiOaelwO%7#+ z799Az|AjP4DCBj{@kr2!R3N(tK+WQ6#9^rK*_Ns%KK$*iG!?4oeKKF z?G!|aX~D}_tGwl%@ku2Y8|5e~y-byB%P)O!)jS^c6>n|`OHx^l$4GCEkd30e@Ft9STf__|0C1u;c+f z4O()AIXoI$OHMUy5pU8zF5^4wGW7pk#y=?KZZ|xjXklEiZ3dP29zY z2}DA+6y2Cpx~HEM90h7#`xpCYJ0_x*l)Ps>pD|!$jZtA>tznqI$YnohMOG-c-fOY? zMgPk+KO?54A`+s_@k?u)ZrYahYtdBjM3bGW)op9vx6LTMT(xXuJ@%ju@tLy=P^5le zWr^@Tjffcd;_w>=Y;a*R-wgexW^XVRiTK%er8l#B9o9&S>*n5`I`$t9XU+A?bJvdn z$|&C5grdyw{P-w9iw0$HO%kpJ&GgK*BOp*+Rb~h156x}VyVfV0L7DcXq8ORR?Et_TZA0gMO z3AU+A=|ygTPDG%s^sy{=L#{eoT=p+0bWC?^{A%n}g6T3dRf;spo+GPokBh)ng^TJSg44!4C0=1wMlF6=O)11mTT>4zN!o9tSF%y}=ZJrLw)V^{`FEY}3ce`Bd^qfiLOZ;58db7-P zx!NS+Bkax+V9)(4;MsflAs69h!r$Q_v~FY)3Bmf%20;q7gr&mchR=!LaCQ52W%cix z5XujvBgkt`v{O$TFCh{xn^tJ*PYh-wmgLx@^Zd@X`!nXqMnCT;z7FY`B3E+m)~F(n z&K-a0d0z30>B~{onc06myWrF&FE@ISU+(GEw z5W_!`1e-w{2L|#RkB2?+OxI3b8`lIp*@%?kWM_R%3k*a;Ax^>S6RV`}L!uF3pYr_! ziG-X{bP^hdLfKB(KhW`EI7;==AUs!$x1k5T1l?81+eqvK7l)_u+<@ z^}{@j<{y=)VJdWz`o~YNs>(!*KLaNhD_`TnbUl}J%)&{-#d?AGCuIOlVXM7A9{%Yb z3QFZtWih2+}y)mwTm~k2|Yh$-2>Cs~6Z|NYqdR;jZ(4H_IM6MAc!fCuaLRfIH0|S_&4C9UJ zQO25NeZrtRU6PE-bJ&bW@N%16x}oP_(rle3^jCFl?OkY#9<7vT`Lg%oCehD+@N@IBJ|iUzBlgCNh{-+R|`h#KORr|6ArY{Rh# zf;O~HG^rFkye!BZ6T6El@)&~#8A->8`i|>bS=%ny#bn^QGHARfl2k}B7ph4${SnJQ zlEr$m6=ABI^%?P7BI6Lof5EGbTOz?bzx82|m#f|jhLO6#&dSh&p-l)QI`N5N%=ATr>_zJFfs8shV?x>q5`X?!&{Pf&uh+C6 zW6&8gOPisqlMukaIi)9YssizvHKDmaQrTp3O-A3JYU1=!>hFi>jk-U1^Q? zTaz%;X{>Vo)x0t3U5!;t6N=R+w!gmva?v12{2-Ulr&2~A2CoyZ1$;;pfoXsZLhVup zuS3%(_lj2D%(q^zyu-s&&x=h<+qiis536GO7#tYuuO{x%NTWNqGIZ5xJ)5<H7Bd^=V6WS`wl# z%;O@2cAJ*>4jaT07%rrQ%7^U^vOTWv^+ME|k3(a6c$#hDZc ziyu^Sp|6v$WR64W>H76TKj~b$&ii{ViF6^s;C64levA9{n{D{SL49G<`f-wNJB0XW zx7S|hv&X%IlrDp{Xeo0p`i*lu04>HlQCr2bloB-XItyzFwulg&Lj) zX>Qb6sPZ*jTjqj(d)l1qHnlVNYQ{Z6X)wudpKY2Z`#6wtM!eEQQy-L!;I7~dMfZSM zOIb2TvCwH|I#D^mWW|4>rVDp3Y>8(2Y=q6_;tePRVzZ{@?BGx4n4Nj^c#f?SCQi8y z0D6n9iqqx~C*csuR6vVHP!w74TMm2VG>nW*HCYt3-jbgRL0FYha7d3hB0$)wSvAf7 zi~dNdTN3O^Rwhwsmje?nF)uqj3Rp~MPCNN81u#8ICgsw zdda+N>YJAvIpyatN10RZv)bSYGP0vB*&Kez^uFdzy!L&FTdA|Yx{`j|y$S?hdpR#~ zRMCMKt37)k7$6!}q*A=(?RqU%Ed;&tF=)2d_N{ZuI zQ$8l9FqMehT%qQVJY0zyu&#=IP=G)=efxd|gEo+fY_?#tS$luZ>!We|jp>>s#N70i zh)8KYVVaQ@iqyb>B{Pm9jdU|F(f(-+ZBY#4=nLc!ifCD7C;7Yl8OJY1U*tnL#urIF zEg)ack4Og?gqyqbKL{o}%41y?i6 ziz?l2=KN#D7649_9pj^=NZj7KYHRb?-F*~QrrI>y{KExxS#>^D^BqY`9J*~sDr^yN z5-x)b_i=}z6G?6DCuV=NE}jf1!w$oDicJv;vu`>v{{=PnEYlBs%Z4mGbkHj; zNtX_zI$6taB8HDQqW`ZE$*H#)?Tat}eSxj)flV?ykY(MvckEe zpC_P?sI9xunR*!H+PUiT1_OiIU~B8D1eo#R#DdtYwP16;k`;GVwZcN>OPH{=2?PSWWLE6)DDl^%`6?!N|dWEd$ znO2kP=D*pXloyKZ?XNMaRZcqXQ1!{vEAuBqyR+RMv9769Lo6qv6a zyK8WVNtg~6g=fTnlj<3go+tcMTreD32YU`V!P!LjF;#N^&x6eJh z9}FR8x0giwzqotL;JCJBX;89c$rf0W#mwAdX31h^W@d{lW@ct)$+DQ4nVFfHnZLHr zJ@=hAbKi^kH!<_8qoa3p$F5$x*2=2Pti%|kT9Li8LwEu?iA(CTO%~0Wn&ywGIGU#Toz8elPLcPHDsbwtI@L7vVd;EY8}8B8 z)_CibJ3x_sOxU_U{ecQr+NO^0*kdoV4uesbhCMr^itJ@?aut#x4FtFgBo8_#uD1~X>G~`$7z)5o3x$?Wo~CP zYi~18_J1!b*1s23>7PZ_V3Qq@@alTEod3R^Igi+zJgs{{78bdD;`*2yFwOQXXIqwQ4J2KPfb^@o9z_FoHR{>X2o14y{OYs3+86@wXtXRpZm<+hi>ZQ02rrijyuN z57Fa=DN%fvROd+(u*=crqWK*^ZA~2CpdX4l*;%G3iMU91p6zB7YHX27-x`dK_xV1? z0reeeO8JjNM>|O}>9O&3PYXr?afu9$7Lm~!AOi`ur_ywe#S!Hj9{OPE23QGeMND(# zS32q~kw>$1RYpr%$?J28a!C6f&88b9d4LwO!Wz2qQvQAy<|}Skk|qb5 za?s7RI|_lKC%B3q=litbW1O=whfNQUdI#KtDXwJC-vZ7Wnwr|KlwGIj+%3%3G?se~ zKr^r9`P^`$%k@}MCga%W3$I>dWh5+&E*nejzZ~M)Y-w4!jVgaL$tIAO z3(kB)J?U2-e3ZBSYYAv2%0BTFZ7!oDv1aHiozp&S_bNDwY_8?6vppS<8~LktX)JuC z-XKk+4byC;1?*JqXax2>Jk0f8^uK?_0#(G)2B5lM!Ifdm5~e8+;MCdf9%WEtpC$3^y;RH_XILB)y?a?ipA9p)qoW|)G(dF@%c}o zP9hc|$WmqYhYyQr#X1D&ZDw1t7#uQ2K+=h`sqN9k|Cv)fNv#L)cn^uC880 z-%x&RufhVUflR@CTFjiBjVUxF_XJB%Aff6Dpw^!2XzVcSf!We5PhprL#lCF8_vMK4 zO$rIcVVumdYhd+yla8>Ify&(^ z&n$LHJl23OkaJU&th`X3)F*w-lv^hBKj`_*S@n>vp|1nVwjX9mkHAEk><=dVNY><4 zY#&%+mY(!sqMjfFBe*-8wGl&#M}?(FUeqRgjv&9h?GtPlJ9E0Sgn_kd^j?)lKcsjy zF)PZC$D>y{^PFow1>6U`1=c5g&x$l(!hkJF&qzF6nd%H%RrTSMN4Zi$H;-#@UHQb6 zcSBjI_rBMYecaR6m5u1>tv&1ZD#>jQY&36)MYf(r;HVSMLtaMf|Dy{_dk9`c;>o*%v`&ToZl8+diG~dRhFXn zybNq9Dw8dHqKXGPadSW;^%~7x;@vMf@hM`nZ?2D|s#3yT3;*J|h2>>)P143~yw31M zhkZ1u%+xI_L)U8W_7T^f8iJ=a6&ZH4c+g$$$@d%Xqg z2%RA$zLd8TO&#d1T4(T)qNOSr>i%p^u<}0pdux*dWsF0mt$k)mT`|4;zDK}iIXUA| zyfzDE{t96_jO0+&C}Q&%F2NI^kN2;E+CWSy;i73Z$RMqleQFFLDdK=oiS z=VK3QxhPFcegP4GQR)i#%6Z1#4bTv1o}M2*IeguC!qoEvPOPV%O`_b>Xxwjo6$E_ptn8wDdnG0lx9V^xv z6I&C7>APU5PuF^p1C`v4?g8w;-im4|vmIA)PVQwzGWZuXX_|JznL8`N)DG&JQtgxy zYFdC=F-NX>(FpsvnCuX^6Vzt3eln~Vdad|(<3>w5*vCB0UuxZl;JUqX*7V-3_drWZ zSy@6z7?@J)fqjOULL;Vp;iuX@q+I0*8}1@v(f;^!* zRxmOsC>@iZ1TwakLdst*ZgVC9w=f0=HiiCNmn&F#da{JxbwL+wv4|q} z_~p7NeT~y>iBUZHh)t6^+)$D38kE7JKfa*&@g`Kxc`o#g2h*CYa9EWrV7UY&6;02xJIMF!TuA*=Vr-x=LDip9i z_Tz=i4c(SG$y=jXn?zF*^S>gx_@$?^R2x2feyT2$dd3+g&s6-EI|iNUW~?HA$_(9J zHIq2ol08XVLL(p^d;Ca&Wl`c{QJA&9#M@NK6suUKmesA@ie^*Obt6q)L$(G7mm14& zxraOKblUf!yxkD!3fY;5JU7Q46pW8C@iGt}f^4IU+1Xj2^<)nI{nv2?BbB zr32xk^+FskM>gl!lp-F(6gMk4iYNm6cGulw*stjm#2N?o z{F{San&HF?A*X(}#B=ifnXpO_yN~)K#R*2Io0$}aN~OC_RbS<822H&`Waj&Jul^rD z3~nc?REzy;G7~U)M{c6QnT)T7Er;9F$IcYZ_3b*sGKvWvjp12r9KSu2RS}FSYG0Ga zMpvA6p@h=GBnv%z4<>l`VACV+&x&S-qkig*ABHv4MwIhl4=~FMb8>h}&4W0`8`k}Y z0>cLwn3{AC8O^|mQWNEUXzpiC;av1mn*np~UoU-O2~-TFS+JC2X&60ue8+TEj(w%C20%Xyw#qybuDyr3LyNE=0xjd5w*GwL|c?y$~C(`JhK|q!|HfRKjrRq)h0&{d`1{so2n*>J%9GJ7%pTQskbw7YzE&o667Zgj+qq!$B@F>iQ+M%d_ zfu*1@t$PH9_YdTLCn}x93~*wo)kI83?162noovlUFyHa`^8UqrF(C0Kie7=@?g#;+ zY4T)0Qv2Gb8LlIn&h7`C$JU@Fzh?>ki2w@5hd~t|pxkCS_~M0f)K0qb zvL8We-7U?0=mD|XL@O61y{*kl4ulx7oSZvt!xxebRdwOxWS~HquynyTF{)TV!vqie z{o9{JhTqfPDei4x`+$%Tc^culO5vnbB)QG}n$&cQW#g~Nj{Nl>44#v&D0ys>N>2r3 z4NOlykv;$KfWrJ@RKk1Wu4!MlD<$k-P7Rw!TFGj#uZN63a9qpGto(tDpY5Zgry=cA z(f*sNQfFIB#9T*m<cgv0`aHw5|`T@__%VCkzi%6L%_ z3i!%fI9f-a8MEt33h zKvbCScvun8iK2I&WAD0dv|1XIFSqex1%9-%A%ilu=v8n--~fXwv!+^Jo(Sw(u*-l~^0+^0xyK z(N)@6n{Eum7>(z~b(T`VG{S@}7%Nk>hGX{-ps=XD9= zYm!?%=bdD;LQ@MUPBC|7_M(oQoR%HcP_-1j@Fy&ZxDj0W&gP}Mt;kaG6td)@(iv}@ zDNX!RQ@72zVdZyQuau$c6IYmyZQN{`^^$n^zws21j#Ey#&MgAW-n`06oa&kOg|pz} zCUJKJn-m=TBSyF5#-Cl`q4ykE6Rd^{*CTQ}#Zz&*v&Y#P%#+LBg6O^(3;-*q87UfuN%Xx(uCsOi3w zm3`SGT5Fe*IFwT@qrX~^aDiJ15nN{~!P0_-1}O$HIx83Cq&s>0i8x%PL7xHahuZe# zkPf4?`qqB!JYOHtee0-X#ZnRP-vS{Al4>x|%Yt$TCkA0kH-ltHXhtxbBDR$?f5H*X z&Sdi=rZ`FK4sNcCm>oyb@gCt~u|I*Idq|RHXs#(KsYHLXu;4tne5pv*rc6AYUB%Yf;bFN3>}DT- zxc8o68+0y}p=M9ZJ!UUmeG~-^DTZ8)DiQ9XNpO>sg@B)sR5s18NzyA0lS^MA`f+v< z;wy)vR%DDnq3Pg78*B!(3l3!D18Ik;a^XwUt*$IfEWd(kIk(k*=gY0e8vcz7+OC-J z3+v*j=dZg*hzm2QUJ&sFY*P==ceoLS?i_YOYok8MEEnk>e&b-L(^K;oB$pPjeQx!c z4Jeuw3S(7zeUY4HOwn9G{lL>>r|K&}DPbTSJNI#8`hAPd2HLKVM?hnf zk<;rJXli#+O8$;Ke5&=z%|Q3(M)n~vWv>aYlSTu{>F97;P?*ZyKXp9?lR+?sY)KX`kQmL?z<;-fn# z9&)b7QqCQ3|c4=LfXxa<&K#<22tzT6TTn$Z;Uf`X{Hmy^qw z|BDg|M34^@238Tgv~2WX2((R$F(n^|5?zb3_<7^U0VB3Grj8B>dC~ZoP*302Pz0ey z&5R_>T6+}|FOYFIi^pA)P#=rq zJC-m-0wU{2+Ur}nO$m2MM_(!d3oSmo>8Ry=qt;hH8ao9YoST~GtV%(bExgd^%%EQP z9!Pt9RC7L=cBM-E$As+xHx`9kl_^gYTc2CKIxbh!c*{I_SZ?vMM9MyP6g<=2&u$na z$tb?B5=t?&`~Lav>sl@37+Wv0;PK^V7hi+q32??3SJ3;Ddf)6YmV}zFWvOoxS`O%F zq(+en0%{!smK*dh^Upa9sQi}L2?d756eH^mkx_ShCve3Q`%2|FDJiIw!v0Xq3TQlu z9{n7&kD<&n>4(BvsU+$`w+EBG{#1OJi{}{~V~|YftES@NDu>4&G>6aF`PSC@%HI24_lFX+gObEzG8|1<`(zvxPy$(m%)ej_blY_6gIjPj!yT!&!GWH|NVS zzw!aj7;mJ)xGvY19RaFZY7bD)bz#_5-W`Hq2RQSI-@{kQzhA{Y>mn?$oce2v@+!w} z$}d-YcEB{+iu9ZOX;upbV~XVSf_{ zAz$ld2vpx!VcTO+^6x&&`-O#(=y@-CG}2#Dsz>MHWEPZYDGeMH+4#i@f(@dA(Siv3 z9PKii_{GH$$@K@7iZG0BWgYCLK{>gD$Hl=Q!dRu`iy(bPnrmsW7!VQcUiIenlT%Wo#456 zJZnX?D?j{NPnxVUEbyt*?R|7^iGN$dkMi~wKWOa@frDs?y;E4(pQ$Y}`_CP2TGIbf zgHsSTBr{)5km{H}#acftT|NpWy`1gT)<+W-N6{4gN=}GoQ;u6o*fE(y6{?Y7aETc+ z*N~#z!@d?aL~=Ea=-)MvXbO3!izjXRC#MV~NXa7QS3qC(eg22|<;wi>FI4?0dh(cC zZ%-S~KMI?&!C;T?P%j}*NOOOJg_6~HKCBa#DCl297=lKKqKIPDJ0VqUbch{siS;yk zqOWGzt4)@4Qq)i#7XN~d@LQvViFJRqBk}zv1aL_IM%CZ`KitXvAE5N_9(1Ohe6`2> z+q=#4s;mGH+27L~qH+H>ob4X8ci9rzAY0F=%fsn%h`2jP-Gxz0q&NKa&BdIclH6ZG z;IFXV2=L>f|1HGv%y&K`XL)1oI!ukUlSKb7@r?{rmO&0kPavY(1>QaZbTgis28qk0 zMvm0$X!j;!ko(oisrowNTm>N<4KYYxQp+{UUVy)<)L%cLwf_GQoe%EibbW|7$1eDf z(i|49PQ*KDlW(m@S`Es-{Dg0xTBgfz8S2sr*}Gl3l-D0z*33k-hR7+xhRCU#&y=V(J18FV&W=;m zn#IqA_?_6>d3q8>yPvym7buM1Uk69Ju3vMX+l?`dv7Nh)xyH|A*j~ji=PbN52GjX+ zZ*K6|jDNG5>8gvh+w(m47+l?ilo53Ae>LI?YD)!9w_yLlGV~}(W`;ch)A3;1O@)Vn->rMlNMOO;`?CGiYU;JL`scOcK|Z< zr*7faVD0qF3nzN)d{H)vMc1BP#Ss^hSUCaY!8?-N(#75ozMp5bMDgWxl1eM%p!AvT zt_7A7^P=2TaKuX}*|_Yoyx2Qw0@`_&0A}QnWtex_og_Heev3}-z7~>Al_4@%et=UW)%)Q{$*lCQA^ES{1OH%Y5N@u& z$iv{XeKiMIzhw)rhAj2QuNn93OKxN=W(kI@)Y+Dc9NR9SqhG+E&mCngJ5%XMs}*ki zLGy({n8xQ>!oW?L2B4iFdwH@d@ z?xZ~fme7qZbXDj)CD!?IrR34=JGE*0d5M!p8c$5G@Y~K>u5nK>z*=T5e|ljM<_lRU zpgHO3BkJ6^!Ba}EhI^dXN(*CzBIjppw#aC2IpGVrCEASl^fn&~Yi0%)mTok!6mTX= zfwXu;EgT#)kTnmFpES>O-{%ns*3p9qlM%U;F-2M|zWl5xKME;M7EpxN@%AmIEc3?_ z<)#m4;vb)QP0dU`NWFdy7X%KNSW4eD<}R#6FsW}wQcp8X@pNn(a~Y~5kQfXpIaYOe z3$bIsH1H5<96TG}yO@m*T?q|=l*c@wtk*x9NBlHmH&2+bjq9j;lL50d`W_JJTN=8| zJN1+P(=)YQL+dR%yyTH~vi7jKRHvb$FcOBeL&1roV+8~KkXdMfN_x#yfAGh5K(t%?8R^KkrOk7Lr>1y>Pk;W!hyKAd5t4MB^-3p=K)-znuaF!I5MSZC zyflK4^|?{q!yt0jOcg;$buUKh(R03fSco4OT>jQHlo{!dA<{fYqtL^-0JG%Bh10`q zn;%#*|eB`qe!?@W(xqa((=R9F4g&*`FbP$G0x@$!U5?aq4 zjZCwRNo=0yaT|5+sKrs4JLO)3ri*#DuRFIZIhqor3_%rM+Oi;5m5AW!PY44Z7xmGe zv9AM4w`WEL4iQB~Els?O&7EhO;i|5c$X1I*u)Q#JEnZx-D$KvGq`-$-vE6kml03N> zxVuK?uCMmAOp^J8@usZiFXUkG*JJIan-@D9FTgRXRe84!jZh9zVQU@za2|y3JEs^f`I{PbUME}CO8C5yra>b9bsc-I^Z48;5wMdf=cMZ z`72qx5gJGl5uV%7tir?9e-kQswE2QpVhRu9T{ihLb0PmFQzVDqRoq@;ZnG)G+y>_t=mc!VP38vf zh^7OgWNbcCz~^h(HlrVGJUWIWKE ziWTuP_*#EPM1HTX%J9}-Ey$x+pOr0T$9-FBzmjf^7JJDfx74;_lbnpM6VS3Cot>X8 z7D0;7S4;wHTSp!;hrl$TJjgUNLg!d{bsk@W5o*Yc{rlv_!@}$kR?gae8JWCb z1iJ$UJlUnhAUgCj?kz{x6+QCZG)K)f7{=WDG9|eQT8Ei65^Duz{ZfI+$S*$*S-2==gvX!AJP?Z$^is1$of!&jVY-SWY z6t`BjUuN?-!GOlFs^F%oFNTmhtK%hGMCl6@?#|M)Oe^4o^@rEMS*!I7qcPd(_`=7{ z2FbJGhqhJ`wKpcjmuqPfs*0L8J_`U1NV;~RTt1ZI;#i{{Tvh;e?7@o}QaLOv7UpCJ zve9ZIKg1cEwC3okEJbn49n0hgodfCeGR4IvXTrc$`2}pnSURp&+l)BT%4X`MRQ*mn zJ-r#EM`rX!ovN^uDjPrq1?^UwLHH}CiXSW;$0MbLx3hH zx!fdl4hYDPb31N}uw)%JRCF?BVB&Qx9Oj3Z(^4+aUu_EU!kn=R^W=`FdGWiTr z2vDPjH(v48xc2FS$sc~^G}L%vV%A%1cPyF=J=NzwkF5THvgzLo|D^Wklm_k-Lu`&^ zCcH07xVUIfy@~x5ZJ`}`Hhn}i;w3#Z)A_n{ZzGDCW#$nFFbD8r#GQv+mx(5&fJ{4XI59^FkGE7F(1(Vk z51GWpusUMVnVuFQX+_rzCk}d`1mqJI#9P?KyA4cN9If!^2953sH~c_shrNo-Z5vAX z=o)Ogl1R+ZWg@fqt%XdXnQ8O@xr1jf`h??t=9YTH8<2a=S{9m7a`yu{->6Fyf=5?zD1q2zMoin}w!uZ9T= ziZ?_~R~SN$)0TC&PR}N4qiqjY*Dn#JNzzOtSnGnG>FWd6&eEKNAH7F9gJyVBn z;fBN?GlHcKsr+G0yR%2>ah+2>x~Wrfp^DTuU_=>-~5IFjIc^AoZJtyoc znd<|BgDwB(;qqJ&)>~4}=U7*!x_z!0H{!Oyr~Rf$o#0AoWEkocog;vU%T}!pTF*^x zU{q**&0px}A(BA8c=l+%v_*K3fu}eMUOW2W*}3Axs7!6=xO6n zJ~hA+629`3{xe9R5Hg0RSpL`iXR@~87=*@O^7w}A6%_RM{x}z_E!j1&SG~i-iBG2i zSC5m=dB(W&J;?H5@Q85>xLC)w=rlhUorW9NVu073j6$Y* zkcy(FfBewt30A64gp6v;!gt8v#WjANf#GRQ)Hc-T<{>3)Hd zJ9&bL0d5sR7!DK}8XTkJpxS!~-_l;>?`bin-Kn;{wjRwi*O}U>j ziFPUVzb)D>DvNFO%w)j4&Ly11?lCN~r5`t=(iw}uWt4Jugaxp*XA%Mfi#q5uwhpIf zjoB0}ost}N@qhu@p49!*@l2<}^D~v$J~aT}*z7Vql`;_iO_08{Pg-B^c8Z9naX7x( zMrF|;5gr&||Fbwe_)$h*(z3RUcUd{hyYJW4SyYJ48ZBdk92*L?66C zKI;>KjXZzp@O(v5vTBlS#-~;#eH)#d4IDIq=hMao!%IIHXb-x=>WzTIBoq(X8!M_E zNuPw#b6v=-VaTP;19Ae+;MRzW>7`qd7@a zIgoaV@cDorhx;Z^Ha8H9`$r=FCe#;Pk8im`8EMWh;6GEuV?KT46I{$ z9PZYTr|*>g+YSeq_FBU`%YLSYE0Ljs`#Bgpaf#6!OJoR{N32zQtEBHlUy@qD1Sek1 z>od(hmXx!YLJPthwi<%_4lUdpch}}mnXVT}=eL{Bro?Kv^`@>E&y&cC4Ts}6f8S?cCf)IgkkZxk7yUAQyc)O;1zUpi@B?Ep4h@v**H?e zx%KxwX;Eyj_;b^xp7UpcQ$t>NZV!adk;EHw<|4uS{J_(*_BXzciD+=56P>jujt+k2 z@ns2(0Wye3bTiiRPx{;S$G42m=K6Aho84yD?fKdl>Sp-)s$G>y>q zFE~49cb7knIr+V9(&uWz*hZR|0-aHoLezwk#kDhb5FC;=o#(&;tECmcDk6PaBdF=s z+2K0N$J{#F%i#<1Z~_7~bckItWs>uILu{-lW6mwvGq3psb#PvYLFsp$`4XlyCfF=R z!ezJq+8qhYHqeJ~Ej|I?z>)9ENfEhwm9G*+gN+f;sZb#Tk+03nJaKA5uG}rdt3UWP zG%3Y@W}(FblQ}lod~l?&!v0dC-J{X`hXk!Fy_p>c=S-nC0ZErF`7)fO&Z=CB6^!}u zTgI<%qmP9hH4A0iXfEhh#+P?Kj>+XX>;t|ok|uGrl6_^ev|66ndl$7OAI38geLwK~ z!&$2JS9-katJPig4otvj@QhbCBK?L|G_Kljk_Fx>IK=QK0_HGA22tK0;wHrso80i8a4-gE!}?nKT} zvW?@65Ug(Ffr$5d^PUu3lso5K-sk<>yJr!`w;)TBoDrJkzya#_1Ih>Msh&{Wo=iW5 z+B@GE)#?(~!EA9Gx+ilTWmZ#!yyi(&*A1hj*njCeqU~y-Yqj8^e1szW$!rbFp=KbH zodVOV4HXng%02E6u(`Ux^OT1I_sNNZ_uWmmjv=*q?E^E}P{>(|tF9L=bLq(?%d!}| zkHqCB;@66(a`0lG|17wN;=LX;I%1+bR2oi*<}r~%G4x`E1Ukwl?_<>>5a65-(i8!k zC0}m|W5SSpIqb{IIHb9tAj9Z#z8-qVZ)!;N&>}BLfu3ixy{BoCQY}Wv#g@^~U~si| zfvW!%xH%I-oZXxw_DsRW94s!!e%trB?W=GVLmVj?(y=lQm(8YBe{X#(oH&XzcX?XBZuEM(e;r}i=yKwU1A5fJx@8bR1_MG#`b)7mh)Ksp zMManp{?m_d-(d9qAS&uo)voc+Kp{6qf&I}YReMh~VdHxLdz3FE4Z(epF!r?1K2nN0 zcv6B*U$3C(bg&(|j-6K|H&=wzUhEd8zs|-&5JZ{v{TMd=@9jWR^zQueRYne-oZD*7 zi{wp6W4R_qTZ|PK80U8qDE|+%Jy}0VgZA#o;uU|Yl>HRlX;1BP^uu_Xyys$J9(%xT zF!VsSe*sr6F~9Q2f~IcFSYe;L@3x+|i4eXolyu`X;}=JI+$BdsD`>^zMy4Hlqt6Ir z`EE_<$kvD)p?FF+FFXoN=`iGU3#D<`>7?NX#`BGSjv_ib?@9`e^rUQ8jvgL+qmaJG z@&g)KGEKm~E*0wp3Zy$Y z2@_lk=c=Dt$n6Ui$6JcYEGWo)1X^#;zrM(PLpOb_K*A;sGf+<)|8$-*mZXdQQ%P+! zf7ySOqBltZ559%^4ft;lC_}S397-R=yjQ|`fqQV;pd?FUx?NGdGu>TxGw3t(qy^>L zeB&e&Lq0KqfuSYJcyP*6xJzuOtyU~<*$rE&+8W}-XcY?w2Ii|NFfhKvLO8w)6|3I< z*Yg$bcdQA-lofU5-%jhadc1dnceYP+O_`pzhR3ft>h!p0ldRoPU$H>}pWX}zLoJLd z3?|9ioWIm59$lVo^@e?+enJj*whP7MG3q4zRK6HDzGtzwTE32NTx=lAc8#b<^eFUI zNT?|7(FMZ%oEu-(C|FZa+K04U_ecE+c}_)9mi5=0xpwhOeoMo=eSZFX?~-MufRER6 zRY$&{oZ>9`3X50Zeh+$y`x0~{z^ZO}sHfFibP>aP*}v*5f4vZ`b=S$RJmf2?%n1OpCGy< zKm&U@#KGdrMwgob*Ti`03tR1?qKldjzj$S$V#1)kjRe-o*0+^!G!btMSzwFvpa#)MSz-FIe6vQOFv-!%km78;M4zaX5qxPr9_c6<-t_ z#s+qsX51WGs6EW)Twn2!t)2H0+3nY7vU>7J;fn2T&$`{V3v=!}c=XO`6N>XX&{<^j z1KOK#+g431a5ZfU#^MxUptO-&{9CTjvWT|7Wueop!MEVhM?~(!rA}#N-Wsj)QY7b> zn)WBnd7>1Ka?n~nLd;R6_9l&m%PyJnDAJ>hCO3NWJl|W9tf-3v2eZ)`O^{-_$=jZ# z7DR%}5xnnstU!NX_mJp2R@!S1JF<$PL6VX;g08aTY5cPKUGlf>$!=>dvQ*{bw!#GelA+w#O?Gn zQ=q#HDqztp@y!9Wv@&gAQxyMU{-SGj7C90Fl+l#OD|wklexZ6{rv2{HLtfgaAk&?= z^_F;FQAAxO(@x1fc+^wp-BlxsY3FcyJ{#F!<<&P1b&^Os)GhbB6h=bOjC$e&S7(G0 ze;|Sy!*MgaOIyGShOn|Z9t4Lx*DwlF2Z;qYWbqn*_D$D@J>@m--v7|RDp#*o<_%Qw zaDAnX5`88`NIJ$$R&-9!H?hd{WHpA;c7F!mW=#?AMM5+}W9&sma9WK?R}>tIY`0Wz zta;(%qcc5;s$osPE&F7&C&DgU2sSRWPy*6uy1aQZM-i!V%w5GLwlRppc)6|hDm_qF z8R1G!F@z*<>^?XNX$uW4B1y%nDlSP3W(PB(9jK(UV2qfdd&@>+6`|AB5<_mfKk_O2oYwFd*%pN|!!V`&t6^&X$}YKi z^X`h~c@4#UX+5fN(hNmXlgFBq(}PU8v$oM`yX4UDhkDf+Inh&cQajCO`0hBdrc(Dj z{@bdX&+2?c(IuaTC;P3YBH0H(Wq>U(Gm_h5-fBFaU(PP_3}kVj$7Q=nqQC~xA(+W@ z))(%KjE5PREY-rrmza`V?jzJRb>MVvh&CtZS-gg6r#Wr6jQOH%>MC+Q#@1yf{0awq z&Q-39GIWu4SV#Nf1m@;;jUh$fsEAY33l9YG#+tcSJQ|O5ehyq*cIqbH9Bz9EqD66a z%(u#jdo_qH_0hm4Do6x6eOAjOrGAt8i9GHZ z`Y}zZZ>ykVBl)UaxSU+>pG>%-9{X|kTy1lx)}nQwxq7RJc*6GkA72l!mZI31er+Dg zM@B^uK5p&Awp8YNbo$#P9w2G_WdjH?l`{;2@+Py@}gu#p}q6nk2-!8NkfO^Nln`-Irj7s^#2ZcULMDJ&9BnarYCM zL_(M9Q-#brqK3rTgyuQTD+%ePiD3{P5?UP;yA$n!m-bY78iDukma4Xxqw_CABEqA6 zd-jM;=BQAblcXwoM6dXR8m!uRm%eEBEJP`cov?qq3woP-8XVy^723}icbczch0WDw zd*c0Ws=6caweh2Eb;sxEHOynCk!` zjSBmnablKgsGhISx@7aM6gGoPan~rj4RZCAk!|lB!hP3@D{+IYq}6+3=_wi>j^g-g z;!$8(5%{{gxMj+9yDi^3KdKAAeKHyCJ`dq37Ho%YWoLsO$8S$}NOHGwiKq(7rqrZgtjmjuWJ^^fH54wM}v?E8R=i<(V)Ugm|K0 z7-y!|q!y0Lr!4I*oa$>sG7_+Je%3IGK=)CN_YJe2-{)*LaoytW{L-L%|D2cm^l zf;iPc^JoEp>-zb~6akTGxvN%=^jOaLhGo*NcJ~lMTN1KXi4!~FsFvFD#U*Z94q$Ch73p%%=dcEq?;78Q8!YmRgLe@G zBGN*XO%SQQG_`vQLEf6XGUQ7g9@Sd@;x`_}#H~QlmA6}A>ZI1;DSycd1)i;&qwxo~ z3#XHavV3_}5af261HO9X&wen^LAoAyXnh|2ExO07_6*U{-b%z-EjSYi48(m!4mXDX zTGW+AesAJ(TJd`FT-=bX^Oq=Mk*)n;Z`|elY0Dj}kd? zT@#5|?s(O8LS+%(N9-FJOigtZXpBK2Vp@qws(mYhau}GE7S*^0g>&)4 zzi;sIm7desJA@OUTJpBI_?l*cT;^OqV97b@%_1t++4Y?y{%5>Kv6=4kKWAO2sSG`($CQU<VRHJ(TNKjoceR9sv0rwIfI!GZ*rBv^pP-4h^KBf)7r zXmEEYxVtqLoZ#+Fg1ftWBf%XSn9jW;cV^!Azh>ss%$oh_d^xMv*{6Efsi%Hbbtm^g znHk@}-cS+;GtdK>N?#2~Xptrr$Y4@xcHMqy}D@Qf{Oi9K1~HJkl04*LzoTGbfGF9BSgqC z1)MAN8F6W4^wA=TlYAP1s@T4|qPV|+KSG-NJ3aG<4h>*3L<>pjH#5zO(|${S_IIBr zy7M6BP5#J7D!)FDB_K~%r(BJpS>(20k+ed@#?C2=hOW>zi5kx85W(Rkk&K&R@(_X37pPfFJVE#= z^`yR5;lD>O(RFKbM7Wtqko5QN{Tt7y+`Ivy6K%2|ts`SwxLyT{E-Dks*=O3UuP+S} zMT-OvCXj&W#BsVsC!EZ)TaD^_%MP^F6Z5^w!agOp+WUp?6;pc?1joyCs641@eZ$H8BwfVwi1 z9C)R@!H_Xd(`!)*w=ke+aC26sG_;f6)zdNNlTOA?gAyHa+u!rS)v`czkos};_LkGmK9jx z^D2k$)lF#@*p$ykPN1*L^OD|i_MH#!)grvm-&k4lpq8Ttd&KvNBywDu!Oyg2c*_x0 zEAHvKCkBR!eT&w&(oz2=O>LR;EnT$FW-^Z2N82}`^%OfwE3eB#`eOOhslra=M$|#r zrU)^yQTER%@xc*GtE%+j>&;VpN`)W>Bw5DK?X!plK_2+@S7q5M^cYf~GVH^4Z2*;K zS)vyOHFJE0qN(hBc>RR!I9z5Vil`%TxVaraSHdD3KexI*!#524$c<&4qVYqz`3d2( zITJHzgc2$xTh&>NMJ<14)~73#Hgw{icKMQ=qA>aF`HI-9Gi`f&@v`zK!JlR*`bDFq`%~vz z4UWdZ%MKSf5Qapb@Cx&qVysK%zh9wZ)Vm@frorDvqes99qU3>@!-nFeQwMX>UE0E! zmpgT4+;{tH=P@kI)H|5{UJbN)b2>7uzyUP(Fy%HVzuYXtQDgawr*UyNy7Vn+?ev0Y*?>oSh}(s8vgLdfGtQ6Znlpid1j2)cVE_%MFD2s;Y`Uzfo?j z_P?z_biLasaqSrufyu{Wg9I%;cfJpEAk5m_-S7t<6|A~xffO$tb>|}V`5^?;RP7~IlCRUQf;~YTXMPCspb(@WR!Z4 z-G)6q*wHP98ZuRX0J08bZzROqv4PI{T^HXRXq$M(+tifMdcmiR=$s}f)7flUUZ}2a zL6sHQejI%(zxLyt0fsH>8TjSrV;C5sdtGw8DLcx4Aa41BniVVv9fY){c>Tz(Ri6P7 zdPvi}_jj0u8F+ue=XIj`CD%#}L+@CYu1<-)4^#r{_&#k} zk;|>HP<RezS{ZhFZsk^yNhQ&Ld*K$!R&JfBzpO^^yRHxd;&fqe-CYLEwt4lMyUtVFZiVwr z^}ondxx=$AaGPLJ*)Y%s$w`PiuAOA3I{I95dv)w9)}kfM9P-rWa@f@V98TvnqAm+ z`K08SWK`ip^>4KUnQrP*dQQo>$8j)8_~4{*Y?`&Xz+YBs?2f0b1!-7?#fBu;BbGaY zpFgGr%=#vzr$#1RjgO7E*Zl4NY35Y)9+DwFr2Kt$$Max!Mk*qaPl1ZN@AwyzeTC;+ z_fy1imCk~qyj&Ees#+3UlN0b9LzOXinQV58y)J_OLl9}mK$DaocNWodof$iz*OlkD ze~VG=}FFCK^*2Y$1B|Ef3 zELhBkfBupn#x<&TDkmSp|8mIOy)%L(R9hJbtTZYmUnW04GM%5HTP$)Vv}5a`Np}R6 z>t5hXOu!qIMp(89+J0v-9`Q_6eto!Jl~^TGNHFr2@x^1o;S%&y&Qf23P| z__wNqvQ8TEy*4A7P!7TxyMYhF9+_c&O|R>*ri=C1Oz%C5v1&l%T#*Vh@>H-v{H=nS z1g8oh5bBSmTDB@1q?a{xhmBIzKSdEeg5zkmlUCES`aXm$3ws^ZWl|7O(K|MS(||=e zPKDopk6=mSGf+M}4La*~=Ju%%Z#SG^qu&m(Xu;l{Si(gAx55DDUh}EmSI3H^w+r?p zle6A^Ctrz;mq36Z9f z%b9xir!0OMBIQOpZo|0&YD!SanI2x<@x%qhDl(WtGJh?!C`cuM4AjTt*ZM7pmYGbc zbF-R(p)O|iV-|qie(ZdG>6$d@OW#^fkt(;Q-X-76wQ`&;mxh0lb&3AfTh;jZ&c z@^WB>Ox>sPRx=1gcLBUHJwff}k9F3&L%fovb)ye;jyhg?ecoJ_B1H+%Sm- zCsXeMqjZY`6!TT4k}|4>o3qRo>KJ=)C=FHK-g9Si7`yzYGaTv_B%ccE+r>8`;3iTl z3RhJfd`HvHKue{z;kqtFfo(R*fhReH;whavqD1#JGY1fn$XCjB6>K9M-C$xpa~SgM zxO=Jf%gDU0nR-?YPGf(8<^HLqfDR)e`U%6G^B=K-mz@jqEQ3PF4cL{zg<++6iN~6> z9WHW6AY))Y)cnPThs}_{Mn|F7oYe;51=t$_rvY@ z(5FVraRqF+L-RW;Z8TxS8Gp$k9>v7Z*OnC3$aMPQQwQJ6%q!0~praRJe>dwdlfZcq z=69m{M@+FBUYG}~4(|2Xi6O}F9~wc6cYi2uHDKqis8m!3SiSxyQ--PQ?lAsCXd(1J zGH6V^egU#H%3onwVVBAS{yt1J#@D>^LkGa*dAbXLdbWVH_5yb}HcvXsv-x*6FdlmP z7IeMWFJ3Ce*}WZ$=p#k^Xw4~0T~T8*?;f=*!;rK_XxeTdJNs(?#&unXF@#`QqWV&T ztqu_JUm5eFy5oGF)Cjpi&-Pa#-6vJo$%}rmb!UY;CvGN~`MBFHCBBWa6}yY~edzi1 z=EZmF6YQHgE#N;9^H~s96>Q)8#yPjj%dtO||J?lH)A);jLgv)C0VhO1lmA7^ymw0S zzX6$1F7^#WU7~CF4c;%Op(;%l@@N@cRFtoLei%Nb$=$=>h^|Pcn?48}GaIvv67pUS zYeVGkIwOS!yRYp9W%A+0Yx^AKV(kHdhWER19G8CZ;BG{HRR#>fdJHr~_|+c%uXQW) z$hAH8?}F(9*YrBkUDVYD&7Y6uJs_A^x66JRHBf$9=)IQ{LQC_$O1zj!PFH_x^~Gxh zbVXM_g zF>ajWb+k(rOy8Kvmg=T$(YZ9)b7L`rXUpapv*9@bJMSVM#MD|JT)!qrEGTgn z6xMTf8|+ZrT{bFb&J-)uHdBJEOioTH{ zypEPHH&JTG;zU2oO)KQ#`X)+Td88>Hn*jcuDeTLQ3D+I%g(?gqq&cMxv-I=t!%=P% z*X9e0weVzhOn-q$(}!-TTL%BpJMcS+$rc?vL<{xazs8dIT`bzFH<+8%+z;k!bG_J` z%S1UQtV#e{ETf-o%<|QqpMKxWL8q2c(+G?1KHqE=uC>w3;)m$Es+7pZXDZKKNv_-j ze=q`HWL?FKfUzo>4S03H$clv$sslz3GcISbY%1y~YzLycnAQOE&*w4yP*`IDYJr(3@y9IF1 zf-((2ag;shn=o%jEGwxDlIfF+Vo9mqSWmk%>6JOf_O*^?G{=|ap~)=*D_lrsOvr<7fYsOsw44iEH)4$I?k*7rXF?1N~h)>-yftLDN}R(v5L#GPi!4gB7}j#;0k z&b9ri=TvahwxveT7cOEAl)a*)$0!Ijdo}S(j+yc4HwmOq%gZ9VcoR7#CM5qjG^rsp z%~0c~p%+nuS~ae->_PR|v~O4gt{!MPT=dII!J10v9QxY<$-$^A-hQ>@%^_89i__W|vlXM<-Xq+Va zy9V7s!f)+ojz-wlcl5Vy5j0TD$VUBxzAT`%$?FiBtIp5m9;zF0b@WiC0lR!_bqcR1 zm#7G6Nh5u{oxI6#B0pCX>?ldJHt7xO2Xe8F4q@#I%R#q5|37tAIAdwSt3oW|1)SBH zQU$i`IRfh~fET2EU}@DYnA4dBq>sEX&(6#nyV;60dvcdPzyoypdq~~$PSHkJgTh!5 zNSggGN<3;Jfysk7#(OdcC&jf@EP=%1+AG4zsfI!NYTIw%43KY>S(CCU|C@YVCIyLP zFHSIP(d;(}UeNbi4w}n7CId#^Hh{ywED(8nPY^@xHF4|pQNg`b=B?uthQ#}6?d2w4 zkWLF;+oqO;n{g`OjuSjf*;90{B21=?BjA8s%<@i=58fw+9L>4mh4IX~CN<9B%C$gE z?F;FJgUsaJ_?le~S5lLQ*-HBW7L!0i#^?0HNnMAl-s@r09)PSFqWC=2^rhOEXntL4 z^HJuYMiPX6&jx$01|~*3OBH1`A^DEJ6>xzU9eY&`YJsn`t%WE6vOR~bp1OMwOLI>l z+1?WBmaF)jSXxa};cwB=5nOciTIQM|Py{!2qXZ!VQ~#tdDM4BMbg`KaK+c8?#Vgs> zN1xcxk%3#=bpFj3+y%P-3s8D*=Yy{|xpbHq@)ALL%}?C|~~v_#30;ro+24As<{pWXI6 zxbcu;@FcPxL$3`hdD)QoVO%}Hn}@(fnmf_onQ!8(72hjwPQ}L%_XAM-jag~F#i|bP zL=O|3ZP?3?1i8)<)w$#eSGTyCM(ob&}$Z441{3ICJjjM@WnXDoY)S6t5?{gwsaD3`xeE5`TFy%PY`a{EhV z+*!|`-zdKH36-y{#d9UQ-61K4JHH`qeO^ec`dNeFv*fB+nd1m3jsYeV`!iu z>&K(om`27^UQVW!iMXNNsQJ#bGVEgEqxUuUszZXffX$&a4sqjT&wo?~4Gt)IujJ`o z7%uSyOMjhz?I8PJxj&G-YUcfCeVr9#!er5M1{d1wy7R&4X^Xt`MabK9VZ81aE?(Ao z2}Ri1f7Qs`3;X|7BU9b>$t(6X$2E?+1^fN%)%T7;-j{teABW?6qZJota?5>mJb}iU z+PRrgcjCteM*{~cn5&~uB<$0Nfmo=7cb3o1$iq!1w*&Wh7NyFw`)Z5ALuKSd445}DHi`wNw+`d z3;?$T>oRs!ZXmY9(nG|`)Gqc86^sW}tjW`Qd*kn76(5Ok{Al%6vee{CZ?49^SVvub zK(qA_xvv0CII18$p1M)5@wg-7j2os-*Rw5xopU^*t!v9SQznDrJh{-J2pwrC&tC14 z3@c!YY@dWu^Vjku5*))*D0)(7od7cPu~I(~gvU&~bgYz->%zFbsgdUf*HvGqF&QwI zI^(`)f(FG5<~sNv4odMPiS~{%EtD+QK=nlKcpKnYMvQ9Z~1F z*TVIt`v`94$?0#$+pw6$^hzr%-fU-b(Olw|<qxlR z=^22chHZaH>VMVu74&iQisYdl7*B(SmnjH?O>!%+gt2coL=9GAmRu}E!~W3Uy!7ls z-f55WNyoB3DU=gRxdfLE>CCQ6QtWKM`t6@227F+5!#SaRG!r%B%7&VrqCC1A8${y) zeJKXw-uknpdh~(f;wAI;)PV8g&*j=PU3GMpizdz6FFF!)<>?hR|TG#Ve^XR=@EI04dah$b{s>)z5xO0 zvn0^oB{K9(JUr!#Bx6QXk^Px6@Fi4|W>6dT(5pz7fSH2-Lteeks6AuCueKCcAAw%1 zR)uaoDElVZ6Rg9vw+YJF4=YUPccU`Rt+=BV$#M@viiw%yGm#Dc}iTHyqGNEas-5oxQ&j8Tt z8>x?fU5&#CJD!-2yHnY~Lk1~d>#>Ei;Trqg&{~xa(m~KANv}LdJ%VN((yAUV*E?`R0)@&Hpo0*^Qb@m5#Zl>47Pq_x&FqT_M%Wy%w>Rt=KNxX$`X7(ZM)wkT0<`ab~s6Ch(KqyNvyC$1Iw+z>;3zI?jAp-`i6bU(NwGHu)-H82Ab@*ls zPHo5)d^^LoBy*l^Q`BPZgv#oeb6FZmG}z1`=!sAdp5Yj)d7c!Ox!gB&$qvhyTH>#a z$oEOeJ-Dj}S?!8m3t!&)c(_pLbzVN%!4kbzUtxxpiEDM{??86Hr8p>4>D{skO@dUBS%Vy>ZzACi;HU zf!m7ttjO%rF;^(P$p|UI;~@l(!;Z(H70gekVV}MdI6XqDwoh4dn%_Ctsj+ItkF=n> z-XzRaOtvPWxe@(akZ@%8@zRdX0RVQoBx|P>lH~V^Nq7b#jIvwvrMp_mcWrD$M$ao# zjaGfWw%Z;&NvV?8m5|Q4S{P4vU-;;I-nax>vDLQVjm4)N+>=CIR~sN#a*kIlQaE?{ zc{y$yq9e6YJ>g2oC#mUf9OhDFd9-bOw>IlU5=>VMRhT_>O_YP~M}5+MHYkEIxVex+ zEYK0u*12Gy>}Iv+n6ujKU)F;C>6^uPwYD|F;R}k!^l4p=RPy$?lt^fw?b-VGiS>)} z=}CW`yGaLYGWvLErTK4%R`0ju0z3+yGAae$d!!Acgk}PX`KENNcDK(_0IgwV;e_4a zEGBY(A0qvjsEH>&J0MbvIO;Vg6cOd`8mltvCbfW(AK(Z%ynz~Q5DVxdUkN`xuOpY7 zm6BaWU3=)VunegfT0%zaYKQo3N~iJ?o)&Jz=kCn`Ubw{dk2DUIJ5`nPr;7iGBo(G% zP=a=Mw@#fUwHO{RPv)k)OWV{94NiG5Wb*=na4+oSy}JMXyZf5WZR1h z`DkQ2@mWZ0<(+fNNL(IKL0MG6c{+yR;LJ`_emwE#)1=8F8~lgdoiU3tfzc}qgI{5h71M`-j8UmdXnRta60fVR^(K!>zV?w}(NmSv3=rD>7f84t@nKT=aP4~~=T%6R}q z&~m7oMIDrcQ9B-gXvl4o;?Ws8sJ%2yc5E{6$4FBzM@VP(DJ|?U_Id{baEH7NiP4nx z^u5!NbiKEW$7v%5eZ%|DYv_a3Pb|T=u-NN4E}ZebB3sVdyyVS;HgVP4EY0t>0KS|DNNOJsjZ4d6>s!I2_Sbc`FxBQ z=HnWqjFh#JUL#WOMW|E?pK7&=e*7uxb3vc2yxjgslaUKq$t`rKQqwcMACo)i3;xc+ z%UIMkj6s3fxAk+RFz$xwZ`Kj8Wg6!1R=LCpsWr)<;WE#2-w-ad(y)+_I+$?A!?teE zo=AU!g^$gF{gZRUX?O^JCg93iW5^r&O6Ii#mB8Ms#bjoaEEf)xnd!hLSjN}CZZ(Rl zHXxyAR<(8SBi~$N(N1GaOvq8Z`LuLDoFPN~X-jWQu-Dnf@U)&+UPr3;dkE$B4@aY` zJU?;#6?6PpkL)UjckgEGgG%%)H>L{AjMCe~B5e>x>h3;UpAx|*pGoL2S-r;jYlXwK zD0Ud%{k7C`#FfPVT7dr-uM9rEOC4BoQg$`o&g`%lc1oUS%jF?2Jc4%)rTO~-lPcfD z%9>o?HE7&B8LZdsG;>CI-t&1pwrcD5PgN97v}!u>1MY}7S-z&ikd^S}ANuKj3p{0$ z8er~`HSBpz($@?sjfaIjb1cUVj7Q7Arp2$peZfB6rUZfe zxO$lpYgJ-wtzdg1$=bZBR}g@y9uoX9Tee@U7@e%lBw$oEHbjd^R$<83 zrC(--pu!JSl@el9IVO!bxVY9+*7LI|w|jO}2L<-Y5m|V&ByE~ptUm(Px%Rn^ty`;2 zvh4N>*i(B#SytF3uDu6BinOL*pcfmdlq@do*1(d%*Ic`82NjJc(<-@gu8lZc9?^E) ztQeacnz@qt**Gcp@rA!JlOxGFJV+10Jgnv_XdI#)791n>i@qIr<@2bk!xYwq>fOSuz`E&L=n4%}Xn+ zs>-JlXU;j+Zs-Cs#MhQnf~7I1nxy~Kpv zy(|lWaOsvM(iOOQ|A<<#GlZI-`6RdBTSpb%HgYm1=ZN)Y2l zP$Bm8g~_#V9booaDHs9a<%G2ON0n8GP^6n!uaa{wE_|erQRQ`HxudR`jN4u01YM!J#8<+~c z?0f;<>C=fL;T2=9C%h=?=beuePwa*#i@)!f0>3z+@5e0Zr0$8iFsK1St4_scD8urA zZiEg_n<4SanW}wBT?fK^Jm#Maudi>mCiPs4W%}yMBS-ea$#IB==H9rbo=_&HA})lmuO>JO#ndxAh*DV)-bN(k zIq%t^nhJkN4^nRdk=a8gwzJ9de1FGbkeOo}7!fG2*ccMK#E$3Vi*~3U) zfezE4zCT~0-NAZE18o)zM}4G5gAS3R)~2up2@5uFuBk6Bmj`) z>XYRgv{@u%_0b~u_;9dk^9wSxI1uyl^%-m>fai)OJCS@%BP&Tx(Cs+4495K~;3YzE zL#1&$Do!Wq+KZiyOs&gh13!-L0YTX+pf2y4uX=y5+f9ALSKGTOdWMGt2qsLA+p%D7>L}Z-3*u}}*agzluq@qnqI*DOn<7Af5fAZkOX1DU6vb7#?+JwIoI2L!WZH<| zvQ4J15!(E6po#dmh#l^GbX1>>!i#s}GlO#Pj$`MA9no^mFRs4|84(`S zm3{#|6$}-#`}3XOok*>q4Ma=g5(viqF156!|MYNFLnQL1tJ*77Em#q(H{cMRhx#~M z;+*|)kk-`_=8Nr3K2rH+uH6j})l-WNGG2EvE4y^Qcvx1&+8q=mR`6>2n8uy^E}cY9 zYn=86p27_MC-3u?PbDr?&CUHkjzI{|qCSvNXHxcaBBU29+6$exg@HatV{(6}!_{-B z)gYiDTmQzvQx8?0S|QXDaPdTUlQ?TB#&PtayQWGsxQJNoW}H@~4JKdt80luBZJ0^y z=6}pRV%T9J2D(=xRP*A9?R^FJIk(UOX=)(KD}C@l1!~mCZ?NR#ol!J8y1u^Ft6eE1 zjIgt*gA!Nft)!eQUA`K#AirmG^Qlt)44k>>iyE8)X-#kKFv^Y+72c9s0-96myJrZh zt1E^%sp;Y)gsza8ga9F*yuN$YehI8%3FFqmZ)5@F7yiro_I)@@^2y>s=|vg(vM~3sysY> zXk{|0yuls`mrqYB{ea1hFSe4+S5$A$p2z(>TJFu>_o6nwo(pz>=1}rd{l9^h48GQZ1qi98LQ1e+exd3ZT9um!2@j#m3pjxg8 zD6vUy26SZ&>ixlWaI@BOj$g41ZzRPt0kLCU(VT{V%)fg=oZVUX+&ejAopDJU@vT>P zEmr_Cpf<)P-B1aP268m}Ja{H!uJk~qC9pK5r+#}owoC+r8^DXmClXx z9nQswG#i}pj^CTuWNpha$GM3Spw8OL#*sKSY4H4QL;0bEzQneAuOsG}*D0vYA1Zpj zqWD!IU*;pLNG9=dQeKrhZZNy(E_v#)!NB6JOPsO{58} z$VaQ*aV}hKDO!O|_LiCM>U|^JVs7i3gd(X7Lc((;;H(w{gWRyhHmHUk9E2<ewuKD`RtQ=4VMK`r~GyPrSyq|SKPdx!`UsCG&VzHLNC>Hs!UYUC(Y6>2t|Co zS3p_j#9#jaC3p-@gx!qeBM=Jwy7(2vc;~mVnu8bKpZ5?FBBgtMACl_h`|qM_Rb2W% zr_eilMw+;E=-iPNV1=N!7=4cy|1?)SF={B2Gv-A!>N59VQE_ZL<$sHicNKB|y7Qd@ g4o%#Da25N6L{((wgg{}^g#cgDpX9~MMfLsv6Awh(L;wH) diff --git a/docs/jiva/README.md b/docs/jiva/README.md deleted file mode 100644 index 20732966..00000000 --- a/docs/jiva/README.md +++ /dev/null @@ -1,90 +0,0 @@ -OpenEBS Logo - -# JIVA Storage Engine Commands - -## Table of Contents -* [Jiva](#jiva) - * [Get Jiva volumes](#get-jiva-volumes) - * [Describe Jiva volumes](#describe-jiva-volumes) - * [Describe Jiva PVCs](#describe-jiva-pvcs) - -* #### `Jiva` - * #### Get `Jiva` volumes - ```bash - $ kubectl openebs get volumes --cas-type=jiva - NAMESPACE NAME STATUS VERSION CAPACITY STORAGE CLASS ATTACHED ACCESS MODE ATTACHED NODE - openebs pvc-478a8329-f02d-47e5-8288-0c28b582be25 RW 2.9.0 4Gi openebs-jiva-csi-sc Released ReadWriteOnce minikube-2 - ``` - Note: For volumes not attached to any application, the `ATTACH NODE` would be shown as `N/A`. - - * #### Describe `Jiva` volumes - ```bash - $ kubectl openebs describe volume pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - - pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca Details : - ----------------- - NAME : pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - ACCESS MODE : ReadWriteOnce - CSI DRIVER : jiva.csi.openebs.io - STORAGE CLASS : openebs-jiva-csi-sc - VOLUME PHASE : Bound - VERSION : 2.12.1 - JVP : jivavolumepolicy - SIZE : 4.0GiB - STATUS : RW - REPLICA COUNT : 1 - - Portal Details : - ------------------ - IQN : iqn.2016-09.com.openebs.jiva:pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - VOLUME NAME : pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - TARGET NODE NAME : minikube - PORTAL : 10.108.189.51:3260 - - Controller and Replica Pod Details : - ----------------------------------- - NAMESPACE NAME MODE NODE STATUS IP READY AGE - jiva pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca-jiva-ctrl-64c964bvtbk5 RW minikube Running 172.17.0.9 1/1 8h25m - jiva pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca-jiva-rep-0 RW minikube Running 172.17.0.10 1/1 8h25m - - Replica Data Volume Details : - ----------------------------- - NAME STATUS VOLUME CAPACITY STORAGECLASS AGE - openebs-pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca-jiva-rep-0 Bound pvc-009a193e-aa44-44d8-8b13-58859ffa734d 4.0GiB openebs-hostpath 8h25m - ``` - - * #### Describe `Jiva` PVCs - ```bash - $ kubectl openebs describe pvc jiva-csi-pvc - - jiva-csi-pvc Details : - ------------------- - NAME : jiva-csi-pvc - NAMESPACE : default - CAS TYPE : jiva - BOUND VOLUME : pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - ATTACHED TO NODE : minikube - JIVA VOLUME POLICY : jivavolumepolicy - STORAGE CLASS : openebs-jiva-csi-sc - SIZE : 4Gi - JV STATUS : RW - PV STATUS : Bound - - Portal Details : - ------------------ - IQN : iqn.2016-09.com.openebs.jiva:pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - VOLUME NAME : pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca - TARGET NODE NAME : minikube - PORTAL : 10.108.189.51:3260 - - Controller and Replica Pod Details : - ----------------------------------- - NAMESPACE NAME MODE NODE STATUS IP READY AGE - jiva pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca-jiva-ctrl-64c964bvtbk5 RW minikube Running 172.17.0.9 1/1 8h24m - jiva pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca-jiva-rep-0 RW minikube Running 172.17.0.10 1/1 8h24m - - Replica Data Volume Details : - ----------------------------- - NAME STATUS VOLUME CAPACITY STORAGECLASS AGE - openebs-pvc-e974f45d-8b8f-4939-954a-607f60a8a5ca-jiva-rep-0 Bound pvc-009a193e-aa44-44d8-8b13-58859ffa734d 4.0GiB openebs-hostpath 8h24m - ``` \ No newline at end of file diff --git a/docs/localpv-lvm/README.md b/docs/localpv-lvm/README.md index 9448a7a3..7c034ff9 100644 --- a/docs/localpv-lvm/README.md +++ b/docs/localpv-lvm/README.md @@ -50,24 +50,24 @@ ``` * #### Describe `LocalPV-LVM` volume ```bash - $ kubectl openebs describe vol pvc-9999274f-ad01-48bc-9b21-7c51b47a870c - - pvc-9999274f-ad01-48bc-9b21-7c51b47a870c Details : + $ kubectl openebs describe vol pvc-5265bc5e-dd55-4272-b1d0-2bb3a172970d + + pvc-5265bc5e-dd55-4272-b1d0-2bb3a172970d Details : ------------------ - Name : pvc-9999274f-ad01-48bc-9b21-7c51b47a870c - Namespace : openebs - AccessMode : ReadWriteOnce - CSIDriver : local.csi.openebs.io - Capacity : 4Gi - PVC : csi-lvmpv - VolumePhase : Bound - StorageClass : openebs-lvmpv - Version : ci - Status : Ready - VolumeGroup : lvmvg - Shared : no - ThinProvisioned : no - NodeID : worker-sh1 + NAME : pvc-5265bc5e-dd55-4272-b1d0-2bb3a172970d + NAMESPACE : lvm + ACCESS MODE : ReadWriteOnce + CSI DRIVER : local.csi.openebs.io + CAPACITY : 5Gi + PVC NAME : csi-lvmpv + VOLUME PHASE : Bound + STORAGE CLASS : openebs-lvmpv + VERSION : 1.4.0 + LVM VOLUME STATUS : Ready + VOLUME GROUP : lvmvg + SHARED : no + THIN PROVISIONED : no + NODE ID : node-0-152720 ``` * #### Describe `LocalPV-LVM` PVCs ```bash diff --git a/go.mod b/go.mod index 63a26779..131f76ab 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,86 @@ module github.com/openebs/openebsctl -go 1.16 +go 1.19 require ( - github.com/docker/go-units v0.4.0 - github.com/ghodss/yaml v1.0.0 + github.com/docker/go-units v0.5.0 github.com/manifoldco/promptui v0.8.0 - github.com/openebs/api/v2 v2.3.0 - github.com/openebs/jiva-operator v1.12.2-0.20210607114402-811a3af7c34a - github.com/openebs/lvm-localpv v0.6.0 - github.com/openebs/zfs-localpv v1.8.0 + github.com/openebs/lvm-localpv v1.4.0 + github.com/openebs/zfs-localpv v1.9.3 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.1.1 - github.com/spf13/viper v1.7.0 - github.com/stretchr/testify v1.6.1 - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/api v0.20.2 - k8s.io/apimachinery v0.20.2 - k8s.io/cli-runtime v0.20.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 + k8s.io/api v0.27.2 + k8s.io/apimachinery v0.27.2 + k8s.io/cli-runtime v0.27.2 k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible - k8s.io/klog v1.0.0 + k8s.io/klog/v2 v2.100.1 +) + +require ( + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.10.2 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 // indirect + k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) -replace k8s.io/client-go => k8s.io/client-go v0.20.2 +replace ( + github.com/docker/go-units => github.com/docker/go-units v0.4.0 + google.golang.org/protobuf => google.golang.org/protobuf v1.33.0 + k8s.io/client-go => k8s.io/client-go v0.27.2 +) diff --git a/go.sum b/go.sum index 2466f55d..ac40090d 100644 --- a/go.sum +++ b/go.sum @@ -8,67 +8,51 @@ cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1 h1:eVvIXUKiTgv++6YnWb42DUA1YL7qDugnKP0HljexdnQ= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= @@ -77,154 +61,170 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= -github.com/container-storage-interface/spec v1.2.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= +github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs= -github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= -github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -242,7 +242,6 @@ github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -251,64 +250,69 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/go-ogle-analytics v0.0.0-20161213085824-14b04e0594ef/go.mod h1:PlwhC7q1VSK73InDzdDatVetQrTsQHIbOvcJAZzitY0= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kubernetes-csi/csi-lib-iscsi v0.0.0-20191120152119-1430b53a1741/go.mod h1:4lv40oTBE8S2UI8H/w0/9GYPPv96vXIwVd/AhU0+ta0= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubernetes-csi/csi-lib-utils v0.6.1/go.mod h1:GVmlUmxZ+SUjVLXicRFjqWUUvWez0g0Y78zNV9t7KfQ= -github.com/kubernetes-csi/csi-lib-utils v0.9.0/go.mod h1:8E2jVUX9j3QgspwHXa6LwyN7IHQDjW9jX3kwoWnSC+M= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -317,151 +321,177 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= -github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= +github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= +github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= -github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/openebs/api/v2 v2.3.0 h1:tkgysm2FnxkkEiC9RxxZ5rTbN4W6iA4qXspcmKRMzPk= -github.com/openebs/api/v2 v2.3.0/go.mod h1:nLCaNvVjgjkjeD2a+n1fMbv5HjoEYP4XB8OAbwmIXtY= -github.com/openebs/jiva-operator v1.12.2-0.20210607114402-811a3af7c34a h1:HuCp3D9TOhJogGTcH5JePJuebceQhPRgB5SizB0bmTg= -github.com/openebs/jiva-operator v1.12.2-0.20210607114402-811a3af7c34a/go.mod h1:5oMQaMQKa0swN1hJnAP7CEMI/MOLVz0S2Mcu0H/l0oc= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= github.com/openebs/lib-csi v0.3.0/go.mod h1:uruyzJiTwRoytQPQXOf4spaezn1cjkiAXjvFGw6aY/8= -github.com/openebs/lib-csi v0.6.0/go.mod h1:KWANWF2zNB8RYyELegid8PxHFrP/cdttR320NA9gVUQ= -github.com/openebs/lvm-localpv v0.6.0 h1:2LWSF/qy6jGKNAALtIN1O5y6tEKhwTGcVUcxy0Qgnpk= -github.com/openebs/lvm-localpv v0.6.0/go.mod h1:DVDU+pjCFdb3rZd4MwVVEZ2eXqq+LT16CpQTm1j0MYo= -github.com/openebs/zfs-localpv v1.8.0 h1:gAIiAHTM+5li7v2K7YWw5toN6XfOOP6hkZqMajim2Aw= -github.com/openebs/zfs-localpv v1.8.0/go.mod h1:9hNu2NhobReX+2JnRy/BcWEJDUW5DyYZfe4dYjHurB4= +github.com/openebs/lvm-localpv v1.4.0 h1:grV0faV35DzPfgBtUtOh8q71igg4zIqFxM0mik0SUlU= +github.com/openebs/lvm-localpv v1.4.0/go.mod h1:sgxacSTz+6qDcoxte3oxN/Mv5AbcwVAKNqCTJyzkNs8= +github.com/openebs/zfs-localpv v1.9.3 h1:OOmw46LyZngyB+2boE5mZGYCFkuzqRXAu4r9Tp11jLE= +github.com/openebs/zfs-localpv v1.9.3/go.mod h1:9hNu2NhobReX+2JnRy/BcWEJDUW5DyYZfe4dYjHurB4= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -471,9 +501,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -484,6 +513,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -504,6 +535,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -520,8 +557,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -530,22 +567,55 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -557,22 +627,18 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -580,32 +646,70 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -619,14 +723,10 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -642,16 +742,35 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= -gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -661,14 +780,21 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -686,9 +812,21 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -697,114 +835,99 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.34.2/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= -k8s.io/api v0.20.0/go.mod h1:HyLC5l5eoS/ygQYl1BXBgFzWNlkHiAuyNAbevIn+FKg= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw= k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= +k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= +k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= -k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/apimachinery v0.20.0/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg= k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA= -k8s.io/cli-runtime v0.20.0 h1:UfTR9vGUWshJpwuekl7MqRmWumNs5tvqPj20qnmOns8= -k8s.io/cli-runtime v0.20.0/go.mod h1:C5tewU1SC1t09D7pmkk83FT4lMAw+bvMDuRxA7f0t2s= -k8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ= -k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= -k8s.io/cloud-provider v0.20.2/go.mod h1:TiVc+qwBh37DNkirzDltXkbR6bdfOjfo243Tv/DyjGQ= -k8s.io/code-generator v0.20.0/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= -k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= +k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= +k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/cli-runtime v0.27.2 h1:9HI8gfReNujKXt16tGOAnb8b4NZ5E+e0mQQHKhFGwYw= +k8s.io/cli-runtime v0.27.2/go.mod h1:9UecpyPDTkhiYY4d9htzRqN+rKomJgyb4wi0OfrmCjw= +k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= +k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= -k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0= -k8s.io/controller-manager v0.20.2/go.mod h1:5FKx8oDeIiQTanQnQNsLxu/8uUEX1TxDXjiSwRxhM+8= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 h1:OmK1d0WrkD3IPfkskvroRykOulHVHf0s0ZIFRjyt+UI= +k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ= k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210111153108-fddb29f9d009 h1:0T5IaWHO3sJTEmCP6mUlBvMukxPKUQWqiI/YuiBNMiQ= -k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= +k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.2.0/go.mod h1:ZHqrRDZi3f6BzONcvlUxkqCKgwasGk5FZrnSv9TVZF4= -sigs.k8s.io/controller-runtime v0.8.2 h1:SBWmI0b3uzMIUD/BIXWNegrCeZmPJ503pOtwxY0LPHM= -sigs.k8s.io/controller-runtime v0.8.2/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= -sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/blockdevice/blockdevice.go b/pkg/blockdevice/blockdevice.go deleted file mode 100644 index 867d86a0..00000000 --- a/pkg/blockdevice/blockdevice.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blockdevice - -import ( - "github.com/docker/go-units" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/printers" -) - -const ( - firstElemPrefix = `├─` - lastElemPrefix = `└─` -) - -// Get manages various implementations of blockdevice listing -func Get(bds []string, openebsNS string) error { - // TODO: Prefer passing the client from outside - k := client.NewK8sClient(openebsNS) - err := createTreeByNode(k, bds) - if err != nil { - return err - } - return nil -} - -// createTreeByNode uses the [node <- list of bds on the node] and creates a tree like output, -// also showing the relevant details to the bds. -func createTreeByNode(k *client.K8sClient, bdNames []string) error { - // 1. Get a list of the BlockDevices - var bdList *v1alpha1.BlockDeviceList - bdList, err := k.GetBDs(bdNames, "") - if err != nil { - return err - } - // 2. Create a map out of the list of bds, by their node names. - var nodeBDlistMap = map[string][]v1alpha1.BlockDevice{} - for _, bd := range bdList.Items { - nodeBDlistMap[bd.Spec.NodeAttributes.NodeName] = append(nodeBDlistMap[bd.Spec.NodeAttributes.NodeName], bd) - } - var rows []metav1.TableRow - if len(nodeBDlistMap) == 0 { - // If there are no block devices show error - return errors.New("no blockdevices found in the " + k.Ns + " namespace") - } - for nodeName, bds := range nodeBDlistMap { - // Create the root, which contains only the node-name - rows = append(rows, metav1.TableRow{Cells: []interface{}{nodeName, "", "", "", "", "", ""}}) - for i, bd := range bds { - // If the bd is the last bd in the list, or the list has only one bd - // append lastElementPrefix before bd name - prefix := "" - if i == len(bds)-1 { - prefix = lastElemPrefix - } else { - prefix = firstElemPrefix - } - rows = append(rows, metav1.TableRow{ - Cells: []interface{}{ - prefix + bd.Name, - bd.Spec.Path, - units.BytesSize(float64(bd.Spec.Capacity.Storage)), - bd.Status.ClaimState, - bd.Status.State, - bd.Spec.FileSystem.Type, - bd.Spec.FileSystem.Mountpoint, - }}) - } - // Add an empty row so that the tree looks neat - rows = append(rows, metav1.TableRow{Cells: []interface{}{"", "", "", "", "", "", ""}}) - } - if len(rows) == 0 { - return util.HandleEmptyTableError("Block Device", k.Ns, "") - } - // Show the output using cli-runtime - util.TablePrinter(util.BDTreeListColumnDefinations, rows, printers.PrintOptions{Wide: true}) - return nil -} diff --git a/pkg/blockdevice/blockdevice_test.go b/pkg/blockdevice/blockdevice_test.go deleted file mode 100644 index db0b4bd5..00000000 --- a/pkg/blockdevice/blockdevice_test.go +++ /dev/null @@ -1,202 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blockdevice - -import ( - "testing" - - openebsFakeClientset "github.com/openebs/api/v2/pkg/client/clientset/versioned/fake" - "github.com/openebs/openebsctl/pkg/client" - "k8s.io/client-go/kubernetes/fake" -) - -func Test_createTreeByNode(t *testing.T) { - k8sCS := fake.NewSimpleClientset() - type args struct { - k *client.K8sClient - bds []string - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid bd inputs and across all namespaces", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: nil, - }, - false, - }, - { - "Test with valid bd inputs and in some valid ns", - args{ - k: &client.K8sClient{ - Ns: "fake-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: nil, - }, - false, - }, - { - "Test with valid bd inputs and in some invalid ns", - args{ - k: &client.K8sClient{ - Ns: "fake-invalid-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: nil, - }, - true, - }, - { - "Test with invalid bd inputs and in some valid ns", - args{ - k: &client.K8sClient{ - Ns: "fake-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(), - }, - bds: nil, - }, - true, - }, - { - "Test with invalid bd inputs across all namespaces", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(), - }, - bds: nil, - }, - true, - }, - { - "Test with valid bd inputs across all namespaces with some valid bd name passed as args", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-3"}, - }, - false, - }, - { - "Test with valid bd inputs across all namespaces with multiple valid bd names passed as args", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-3", "some-fake-bd-2"}, - }, - false, - }, - { - "Test with valid bd inputs across all namespaces with some valid and some invalid bd names passed as args", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-365", "some-fake-bd-2"}, - }, - false, - }, - { - "Test with valid bd inputs across all namespaces with some invalid bd name passed as args", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-365"}, - }, - true, - }, - { - "Test with valid bd inputs in a namespace with some valid bd name passed as args", - args{ - k: &client.K8sClient{ - Ns: "fake-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-3"}, - }, - false, - }, - { - "Test with valid bd inputs in an invalid namespace with some valid bd name passed as args", - args{ - k: &client.K8sClient{ - Ns: "fake-invalid-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-3"}, - }, - true, - }, - { - "Test with valid bd inputs in a valid namespace with some valid bd name passed as args", - args{ - k: &client.K8sClient{ - Ns: "fake-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-3"}, - }, - false, - }, - { - "Test with valid bd inputs in a valid namespace with some invalid bd name passed as args", - args{ - k: &client.K8sClient{ - Ns: "fake-ns", - K8sCS: k8sCS, - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&bd1, &bd2, &bd3), - }, - bds: []string{"some-fake-bd-365"}, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := createTreeByNode(tt.args.k, tt.args.bds); (err != nil) != tt.wantErr { - t.Errorf("createTreeByNode() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/blockdevice/testdata_test.go b/pkg/blockdevice/testdata_test.go deleted file mode 100644 index 5102122f..00000000 --- a/pkg/blockdevice/testdata_test.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blockdevice - -import ( - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var ( - bd1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "some-fake-bd-1"}, - Spec: v1alpha1.DeviceSpec{ - Path: "/dev/sdb", - Capacity: v1alpha1.DeviceCapacity{Storage: uint64(132131321)}, - FileSystem: v1alpha1.FileSystemInfo{ - Type: "zfs_member", - Mountpoint: "/var/some-fake-point", - }, - NodeAttributes: v1alpha1.NodeAttribute{ - NodeName: "fake-node-1", - }, - }, - Status: v1alpha1.DeviceStatus{ - ClaimState: "Claimed", - State: "Active", - }, - } - bd2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "some-fake-bd-2"}, - Spec: v1alpha1.DeviceSpec{ - Path: "/dev/sdb", - Capacity: v1alpha1.DeviceCapacity{Storage: uint64(132131321)}, - FileSystem: v1alpha1.FileSystemInfo{ - Type: "zfs_member", - Mountpoint: "/var/some-fake-point", - }, - NodeAttributes: v1alpha1.NodeAttribute{ - NodeName: "fake-node-1", - }, - }, - Status: v1alpha1.DeviceStatus{ - ClaimState: "Claimed", - State: "Active", - }, - } - bd3 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "some-fake-bd-3", Namespace: "fake-ns"}, - Spec: v1alpha1.DeviceSpec{ - Path: "/dev/sdb", - Capacity: v1alpha1.DeviceCapacity{Storage: uint64(132131321)}, - FileSystem: v1alpha1.FileSystemInfo{ - Type: "lvm_member", - Mountpoint: "/var/some-fake-point", - }, - NodeAttributes: v1alpha1.NodeAttribute{ - NodeName: "fake-node-2", - }, - }, - Status: v1alpha1.DeviceStatus{ - ClaimState: "Claimed", - State: "Active", - }, - } -) diff --git a/pkg/client/bd.go b/pkg/client/bd.go deleted file mode 100644 index 50b554a1..00000000 --- a/pkg/client/bd.go +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package client - -import ( - "context" - - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - "github.com/pkg/errors" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - // required for auth, see: https://github.com/kubernetes/client-go/tree/v0.17.3/plugin/pkg/client/auth - _ "k8s.io/client-go/plugin/pkg/client/auth" -) - -// GetBD returns the BlockDevice passed as name with OpenEBS's Client -func (k K8sClient) GetBD(bd string) (*v1alpha1.BlockDevice, error) { - blockDevice, err := k.OpenebsCS.OpenebsV1alpha1().BlockDevices(k.Ns).Get(context.TODO(), bd, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting block device") - } - return blockDevice, nil -} - -// GetBDs returns a list of BlockDevices based on the values of bdNames slice. -// bdNames slice if is nil or empty, it returns all the BDs in the cluster. -// bdNames slice if is not nil or not empty, it return the BDs whose names are present in the slice. -// labelselector takes the label(key+value) and makes an api call with this filter applied. Can be empty string if label filtering is not needed. -func (k K8sClient) GetBDs(bdNames []string, labelselector string) (*v1alpha1.BlockDeviceList, error) { - bds, err := k.OpenebsCS.OpenebsV1alpha1().BlockDevices(k.Ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting block device") - } - if len(bdNames) == 0 { - return bds, nil - } - bdNameBDmap := make(map[string]v1alpha1.BlockDevice) - for _, item := range bds.Items { - bdNameBDmap[item.Name] = item - } - var items = make([]v1alpha1.BlockDevice, 0) - for _, name := range bdNames { - if _, ok := bdNameBDmap[name]; ok { - items = append(items, bdNameBDmap[name]) - } - } - return &v1alpha1.BlockDeviceList{ - Items: items, - }, nil -} - -// GetBDCs returns a list of BlockDeviceClaims based on the values of bdcNames slice. -// bdcNames slice if is nil or empty, it returns all the BDCs in the cluster. -// bdcNames slice if is not nil or not empty, it return the BDCs whose names are present in the slice. -// labelselector takes the label(key+value) and makes an api call with this filter applied. Can be empty string if label filtering is not needed. -func (k K8sClient) GetBDCs(bdcNames []string, labelselector string) (*v1alpha1.BlockDeviceClaimList, error) { - bds, err := k.OpenebsCS.OpenebsV1alpha1().BlockDeviceClaims(k.Ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting block device") - } - if len(bdcNames) == 0 { - return bds, nil - } - bdcNameBDCmap := make(map[string]v1alpha1.BlockDeviceClaim) - for _, item := range bds.Items { - bdcNameBDCmap[item.Name] = item - } - var items = make([]v1alpha1.BlockDeviceClaim, 0) - for _, name := range bdcNames { - if _, ok := bdcNameBDCmap[name]; ok { - items = append(items, bdcNameBDCmap[name]) - } - } - return &v1alpha1.BlockDeviceClaimList{ - Items: items, - }, nil -} diff --git a/pkg/client/cstor.go b/pkg/client/cstor.go deleted file mode 100644 index 88d5b2e7..00000000 --- a/pkg/client/cstor.go +++ /dev/null @@ -1,294 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package client - -import ( - "context" - "fmt" - - "github.com/openebs/openebsctl/pkg/util" - "github.com/pkg/errors" - - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" - - // required for auth, see: https://github.com/kubernetes/client-go/tree/v0.17.3/plugin/pkg/client/auth - _ "k8s.io/client-go/plugin/pkg/client/auth" -) - -// GetCV returns the CStorVolume passed as name with OpenEBS's Client -func (k K8sClient) GetCV(volName string) (*cstorv1.CStorVolume, error) { - volInfo, err := k.OpenebsCS.CstorV1().CStorVolumes(k.Ns).Get(context.TODO(), volName, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "error while getting volume %s", volName) - } - return volInfo, nil -} - -// GetCVs returns a list or map of CStorVolumes based on the values of volNames slice, and options. -// volNames slice if is nil or empty, it returns all the CVs in the cluster. -// volNames slice if is not nil or not empty, it return the CVs whose names are present in the slice. -// rType takes the return type of the method, can be either List or Map. -// labelselector takes the label(key+value) and makes an api call with this filter applied, can be empty string if label filtering is not needed. -// options takes a MapOptions object which defines how to create a map, refer to types for more info. Can be empty in case of rType is List. -// Only one type can be returned at a time, please define the other type as '_' while calling. -func (k K8sClient) GetCVs(volNames []string, rType util.ReturnType, labelSelector string, options util.MapOptions) (*cstorv1.CStorVolumeList, map[string]cstorv1.CStorVolume, error) { - cVols, err := k.OpenebsCS.CstorV1().CStorVolumes("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) - if len(cVols.Items) == 0 { - return nil, nil, errors.Errorf("Error while getting volumes%v", err) - } - var list []cstorv1.CStorVolume - if len(volNames) == 0 { - list = cVols.Items - } else { - csMap := make(map[string]cstorv1.CStorVolume) - for _, cv := range cVols.Items { - csMap[cv.Name] = cv - } - for _, name := range volNames { - if cv, ok := csMap[name]; ok { - list = append(list, cv) - } else { - fmt.Printf("Error from server (NotFound): cStorVolume %s not found\n", name) - } - } - } - if rType == util.List { - return &cstorv1.CStorVolumeList{ - Items: list, - }, nil, nil - } - if rType == util.Map { - cvMap := make(map[string]cstorv1.CStorVolume) - switch options.Key { - case util.Label: - for _, cv := range list { - if vol, ok := cv.Labels[options.LabelKey]; ok { - cvMap[vol] = cv - } - } - return nil, cvMap, nil - case util.Name: - for _, cv := range list { - cvMap[cv.Name] = cv - } - return nil, cvMap, nil - default: - return nil, nil, errors.New("invalid map options") - } - } - return nil, nil, errors.New("invalid return type") -} - -// GetCVA returns the CStorVolumeAttachment, corresponding to the label passed. -// Ex:- labelSelector: {cstortypes.PersistentVolumeLabelKey + "=" + pvName} -func (k K8sClient) GetCVA(labelSelector string) (*cstorv1.CStorVolumeAttachment, error) { - vol, err := k.OpenebsCS.CstorV1().CStorVolumeAttachments("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) - if err != nil { - return nil, errors.Wrap(err, "error from server (NotFound): CVA not found") - } else if vol == nil || len(vol.Items) == 0 { - return nil, fmt.Errorf("error from server (NotFound): CVA not found for %s", labelSelector) - } - return &vol.Items[0], nil -} - -// GetCVAs returns a list or map of CStorVolumeAttachments based on the values of options. -// rType takes the return type of the method, can either be List or Map. -// labelselector takes the label(key+value) and makes a api call with this filter applied, can be empty string if label filtering is not needed. -// options takes a MapOptions object which defines how to create a map, refer to types for more info. Can be empty in case of rType is List. -// Only one type can be returned at a time, please define the other type as '_' while calling. -func (k K8sClient) GetCVAs(rType util.ReturnType, labelSelector string, options util.MapOptions) (*cstorv1.CStorVolumeAttachmentList, map[string]cstorv1.CStorVolumeAttachment, error) { - cvaList, err := k.OpenebsCS.CstorV1().CStorVolumeAttachments("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) - if len(cvaList.Items) == 0 { - return nil, nil, errors.Errorf("No CVA found for %s, %v", labelSelector, err) - } - if rType == util.List { - return cvaList, nil, nil - } - if rType == util.Map { - cvaMap := make(map[string]cstorv1.CStorVolumeAttachment) - switch options.Key { - case util.Label: - for _, cva := range cvaList.Items { - if vol, ok := cva.Labels[options.LabelKey]; ok { - cvaMap[vol] = cva - } - } - return nil, cvaMap, nil - case util.Name: - for _, cva := range cvaList.Items { - cvaMap[cva.Name] = cva - } - return nil, cvaMap, nil - default: - return nil, nil, errors.New("invalid map options") - } - } - return nil, nil, errors.New("invalid return type") -} - -// GetCVTargetPod returns the Cstor Volume Target Pod, corresponding to the volumeClaim and volumeName. -func (k K8sClient) GetCVTargetPod(volumeClaim string, volumeName string) (*corev1.Pod, error) { - pods, err := k.K8sCS.CoreV1().Pods(k.Ns).List(context.TODO(), metav1.ListOptions{LabelSelector: fmt.Sprintf("openebs.io/persistent-volume-claim=%s,openebs.io/persistent-volume=%s,openebs.io/target=cstor-target", volumeClaim, volumeName)}) - if err != nil || len(pods.Items) == 0 { - return nil, errors.New("The target pod for the volume was not found") - } - return &pods.Items[0], nil -} - -// GetCVInfoMap returns a Volume object, filled using corresponding CVA and PV. -func (k K8sClient) GetCVInfoMap() (map[string]*util.Volume, error) { - volumes := make(map[string]*util.Volume) - cstorVA, _, err := k.GetCVAs(util.List, "", util.MapOptions{}) - if err != nil { - return volumes, errors.Wrap(err, "error while getting storage volume attachments") - } - for _, i := range cstorVA.Items { - if i.Spec.Volume.Name == "" { - continue - } - pv, err := k.GetPV(i.Spec.Volume.Name) - if err != nil { - klog.Errorf("Failed to get PV %s", i.ObjectMeta.Name) - continue - } - vol := &util.Volume{ - StorageClass: pv.Spec.StorageClassName, - Node: i.Labels["nodeID"], - PVC: pv.Spec.ClaimRef.Name, - CSIVolumeAttachmentName: i.Name, - AttachementStatus: string(pv.Status.Phase), - // first fetch access modes & then convert to string - AccessMode: util.AccessModeToString(pv.Spec.AccessModes), - } - // map the pv name to the vol obj - volumes[i.Spec.Volume.Name] = vol - } - return volumes, nil -} - -// GetCVBackups returns the CStorVolumeBackup, corresponding to the label passed. -// Ex:- labelSelector: {cstortypes.PersistentVolumeLabelKey + "=" + pvName} -func (k K8sClient) GetCVBackups(labelselector string) (*cstorv1.CStorBackupList, error) { - cstorBackupList, err := k.OpenebsCS.CstorV1().CStorBackups("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil || len(cstorBackupList.Items) == 0 { - return nil, errors.New("no cstorbackups were found for the volume") - } - return cstorBackupList, nil -} - -// GetCVCompletedBackups returns the CStorCompletedBackups, corresponding to the label passed. -// Ex:- labelSelector: {cstortypes.PersistentVolumeLabelKey + "=" + pvName} -func (k K8sClient) GetCVCompletedBackups(labelselector string) (*cstorv1.CStorCompletedBackupList, error) { - cstorCompletedBackupList, err := k.OpenebsCS.CstorV1().CStorCompletedBackups("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil || len(cstorCompletedBackupList.Items) == 0 { - return nil, errors.New("no cstorcompletedbackups were found for the volume") - } - return cstorCompletedBackupList, nil -} - -// GetCVRestores returns the CStorRestores, corresponding to the label passed. -// Ex:- labelSelector: {cstortypes.PersistentVolumeLabelKey + "=" + pvName} -func (k K8sClient) GetCVRestores(labelselector string) (*cstorv1.CStorRestoreList, error) { - cStorRestoreList, err := k.OpenebsCS.CstorV1().CStorRestores("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil || len(cStorRestoreList.Items) == 0 { - return nil, errors.New("no cstorrestores were found for the volume") - } - return cStorRestoreList, nil -} - -// GetCVC returns the CStorVolumeConfig for cStor volume using the PV/CV/CVC name. -func (k K8sClient) GetCVC(name string) (*cstorv1.CStorVolumeConfig, error) { - cStorVolumeConfig, err := k.OpenebsCS.CstorV1().CStorVolumeConfigs(k.Ns).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "error while getting cStor Volume Config for %s in %s", name, k.Ns) - } - return cStorVolumeConfig, nil -} - -// GetCVRs returns the list CStorVolumeReplica, corresponding to the label passed. -// For ex:- labelselector : {"cstorvolume.openebs.io/name" + "=" + name} , {"cstorpoolinstance.openebs.io/name" + "=" + poolName} -func (k K8sClient) GetCVRs(labelselector string) (*cstorv1.CStorVolumeReplicaList, error) { - cvrs, err := k.OpenebsCS.CstorV1().CStorVolumeReplicas("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil { - return nil, errors.Wrapf(err, "error while getting cStor Volume Replica for %s", labelselector) - } - if cvrs == nil || len(cvrs.Items) == 0 { - fmt.Printf("Error while getting cStor Volume Replica for %s, no replicas found for \n", labelselector) - } - return cvrs, nil -} - -// GetCSPC returns the CStorPoolCluster for cStor volume using the poolName passed. -func (k K8sClient) GetCSPC(poolName string) (*cstorv1.CStorPoolCluster, error) { - cStorPool, err := k.OpenebsCS.CstorV1().CStorPoolClusters(k.Ns).Get(context.TODO(), poolName, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting cspc") - } - return cStorPool, nil -} - -func (k K8sClient) ListCSPC() (*cstorv1.CStorPoolClusterList, error) { - cStorPool, err := k.OpenebsCS.CstorV1().CStorPoolClusters(k.Ns).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting cspc") - } - return cStorPool, nil -} - -// GetCSPI returns the CStorPoolInstance for cStor volume using the poolName passed. -func (k K8sClient) GetCSPI(poolName string) (*cstorv1.CStorPoolInstance, error) { - cStorPool, err := k.OpenebsCS.CstorV1().CStorPoolInstances(k.Ns).Get(context.TODO(), poolName, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting cspi") - } - return cStorPool, nil -} - -// GetCSPIs returns a list of CStorPoolInstances based on the values of cspiNames slice -// cspiNames slice if is nil or empty, it returns all the CSPIs in the cluster -// cspiNames slice if is not nil or not empty, it return the CSPIs whose names are present in the slice -// labelselector takes the label(key+value) and makes an api call with this filter applied. Can be empty string if label filtering is not needed. -func (k K8sClient) GetCSPIs(cspiNames []string, labelselector string) (*cstorv1.CStorPoolInstanceList, error) { - cspi, err := k.OpenebsCS.CstorV1().CStorPoolInstances("").List(context.TODO(), metav1.ListOptions{LabelSelector: labelselector}) - if err != nil { - return nil, errors.Wrapf(err, "Error while getting cspi") - } - if len(cspiNames) == 0 { - return cspi, nil - } - poolMap := make(map[string]cstorv1.CStorPoolInstance) - for _, p := range cspi.Items { - poolMap[p.Name] = p - } - var list []cstorv1.CStorPoolInstance - for _, name := range cspiNames { - if pool, ok := poolMap[name]; ok { - list = append(list, pool) - } - // else { - // This logging might be omitted - // fmt.Fprintf(os.Stderr, "Error from server (NotFound): pool %s not found\n", name) - //} - } - return &cstorv1.CStorPoolInstanceList{ - Items: list, - }, nil -} diff --git a/pkg/client/jiva.go b/pkg/client/jiva.go deleted file mode 100644 index 47929742..00000000 --- a/pkg/client/jiva.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package client - -import ( - "context" - "fmt" - - "github.com/openebs/openebsctl/pkg/util" - "github.com/pkg/errors" - - jiva "github.com/openebs/jiva-operator/pkg/apis/openebs/v1alpha1" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - // required for auth, see: https://github.com/kubernetes/client-go/tree/v0.17.3/plugin/pkg/client/auth - _ "k8s.io/client-go/plugin/pkg/client/auth" -) - -// GetJV returns the JivaVolume passed as name with REST Client -func (k K8sClient) GetJV(jv string) (*jiva.JivaVolume, error) { - var j jiva.JivaVolume - err := k.K8sCS.Discovery().RESTClient().Get().Namespace(k.Ns).Name(jv).AbsPath("/apis/openebs.io/v1alpha1"). - Resource("jivavolumes").Do(context.TODO()).Into(&j) - if err != nil { - return nil, err - } - return &j, nil -} - -// GetJVs returns a list or map of JivaVolumes based on the values of volNames slice, and options. -// volNames slice if is nil or empty, it returns all the JVs in the cluster. -// volNames slice if is not nil or not empty, it return the JVs whose names are present in the slice. -// rType takes the return type of the method, can either List or Map. -// labelselector takes the label(key+value) and makes an api call with this filter applied, can be empty string if label filtering is not needed. -// options takes a MapOptions object which defines how to create a map, refer to types for more info. Can be empty in case of rType is List. -// Only one type can be returned at a time, please define the other type as '_' while calling. -func (k K8sClient) GetJVs(volNames []string, rType util.ReturnType, labelSelector string, options util.MapOptions) (*jiva.JivaVolumeList, map[string]jiva.JivaVolume, error) { - jvs := jiva.JivaVolumeList{} - // NOTE: The resource name must be plural and the API-group should be present for getting CRs - err := k.K8sCS.Discovery().RESTClient().Get().AbsPath("/apis/openebs.io/v1alpha1"). - Resource("jivavolumes").Do(context.TODO()).Into(&jvs) - if err != nil { - return nil, nil, err - } - var list []jiva.JivaVolume - if len(volNames) == 0 { - list = jvs.Items - } else { - jvsMap := make(map[string]jiva.JivaVolume) - for _, jv := range jvs.Items { - jvsMap[jv.Name] = jv - } - for _, name := range volNames { - if jv, ok := jvsMap[name]; ok { - list = append(list, jv) - } else { - fmt.Printf("Error from server (NotFound): jivavolume %s not found\n", name) - } - } - } - if rType == util.List { - return &jiva.JivaVolumeList{ - Items: list, - }, nil, nil - } - if rType == util.Map { - jvMap := make(map[string]jiva.JivaVolume) - switch options.Key { - case util.Label: - for _, jv := range list { - if vol, ok := jv.Labels[options.LabelKey]; ok { - jvMap[vol] = jv - } - } - return nil, jvMap, nil - case util.Name: - for _, jv := range list { - jvMap[jv.Name] = jv - } - return nil, jvMap, nil - default: - return nil, nil, errors.New("invalid map options") - } - } - return nil, nil, errors.New("invalid return type") -} - -// GetJVTargetPod returns the Jiva Volume Controller and Replica Pods, corresponding to the volumeName. -func (k K8sClient) GetJVTargetPod(volumeName string) (*corev1.PodList, error) { - pods, err := k.K8sCS.CoreV1().Pods(k.Ns).List(context.TODO(), metav1.ListOptions{LabelSelector: fmt.Sprintf("openebs.io/cas-type=jiva,openebs.io/persistent-volume=%s", volumeName)}) - if err != nil || len(pods.Items) == 0 { - return nil, errors.New("The controller and replica pod for the volume was not found") - } - return pods, nil -} diff --git a/pkg/client/k8s.go b/pkg/client/k8s.go index 8274621a..96b4fed3 100644 --- a/pkg/client/k8s.go +++ b/pkg/client/k8s.go @@ -17,27 +17,19 @@ limitations under the License. package client import ( - "bytes" "context" "fmt" - "io" "log" "os" "path/filepath" "strings" - "time" - - "github.com/ghodss/yaml" lvmclient "github.com/openebs/lvm-localpv/pkg/generated/clientset/internalclientset" "github.com/openebs/openebsctl/pkg/util" zfsclient "github.com/openebs/zfs-localpv/pkg/generated/clientset/internalclientset" "github.com/pkg/errors" - openebsclientset "github.com/openebs/api/v2/pkg/client/clientset/versioned" - appsv1 "k8s.io/api/apps/v1" - batchV1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -61,9 +53,6 @@ type K8sClient struct { // K8sCS refers to the Clientset capable of communicating // with the K8s cluster K8sCS kubernetes.Interface - // OpenebsClientset capable of accessing the OpenEBS - // components - OpenebsCS openebsclientset.Interface // LVMCS is the client for accessing OpenEBS LVM components LVMCS lvmclient.Interface // ZFCS is the client for accessing OpenEBS ZFS components @@ -108,18 +97,13 @@ func newK8sClient(ns string) (*K8sClient, error) { if err != nil { return nil, errors.Wrap(err, "failed to build Kubernetes clientset") } - openebsCS, err := getOpenEBSClient(config) - if err != nil { - return nil, errors.Wrap(err, "failed to build OpenEBS clientset") - } lv, _ := getLVMclient(config) zf, _ := getZFSclient(config) return &K8sClient{ - Ns: ns, - K8sCS: k8sCS, - OpenebsCS: openebsCS, - LVMCS: lv, - ZFCS: zf, + Ns: ns, + K8sCS: k8sCS, + LVMCS: lv, + ZFCS: zf, }, nil } @@ -161,20 +145,6 @@ func getK8sClient(kubeconfig string) (*kubernetes.Clientset, error) { return clientset, nil } -// getOpenEBSClient returns OpenEBS clientset by taking kubeconfig as an -// argument -func getOpenEBSClient(kubeconfig string) (*openebsclientset.Clientset, error) { - config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - return nil, errors.Wrap(err, "Could not build config from flags") - } - client, err := openebsclientset.NewForConfig(config) - if err != nil { - return nil, errors.Wrap(err, "Could not get new config") - } - return client, nil -} - func homeDir() string { if h := os.Getenv("HOME"); h != "" { return h @@ -196,7 +166,7 @@ func (k K8sClient) GetOpenEBSNamespace(casType string) (string, error) { return pods.Items[0].Namespace, nil } -// GetOpenEBSNamespaceMap maps the cas-type to it's namespace, e.g. n[cstor] = cstor-ns +// GetOpenEBSNamespaceMap maps the cas-type to it's namespace, e.g. n[zfs] = zfs-ns // NOTE: This will not work correctly if CSI controller pod runs in kube-system NS func (k K8sClient) GetOpenEBSNamespaceMap() (map[string]string, error) { label := "openebs.io/component-name in (" @@ -418,128 +388,6 @@ func (k K8sClient) GetPVCs(namespace string, pvcNames []string, labelselector st }, nil } -/* - UPGRADE SPECIFIC METHODS -*/ - -// Create Batch Job From a JobSpec Object -func (k K8sClient) CreateBatchJob(jobSpec *batchV1.Job, namespace string) { - jobs := k.K8sCS.BatchV1().Jobs(namespace) - - // 1. Do a dry-run to check if the process can went without problems - fmt.Println("Creating Dry-run job...") - _, err := jobs.Create(context.TODO(), jobSpec, metav1.CreateOptions{DryRun: []string{"All"}}) - if err != nil { - fmt.Fprint(os.Stderr, "Dry-run failed: ", err) - fmt.Println() - return - } - - // The trial code to show Job before starting - // Needs another approach, since it is throwing a lot of things which is not needed - out, err := yaml.Marshal(jobSpec) - if err != nil { - fmt.Println("error Marshalling yaml:", err) - } - s := string(out[:]) - fmt.Println(s) - - if contnue := util.PromptToStartAgain("Continue?", true); !contnue { - fmt.Println("Job not started on user's choice") - os.Exit(0) - } - - // 2. Create an actual persisted job run - fmt.Println("Creating a batch job...") - _, err = jobs.Create(context.TODO(), jobSpec, metav1.CreateOptions{}) - - if err != nil { - fmt.Fprint(os.Stderr, "Job creation failed: ", err) - return - } - - fmt.Println("Job Created successfully: ", jobSpec.ObjectMeta.Name) -} - -// GetBatchJob returns batch-job by name -func (k K8sClient) GetBatchJob(name string, namespace string) (*batchV1.Job, error) { - job, err := k.K8sCS.BatchV1().Jobs(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return job, nil -} - -// GetBatchJobs returns batch jobs running in the namespace with the label -func (k K8sClient) GetBatchJobs(namespace string, labelSelector string) (*batchV1.JobList, error) { - list, err := k.K8sCS.BatchV1().Jobs(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) - if err != nil { - return nil, err - } - - return list, nil -} - -// Delete a Batch Job by name -func (k K8sClient) DeleteBatchJob(name string, namespace string) error { - return k.K8sCS.BatchV1().Jobs(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) -} - -// GetPodLogs returns logs from the containers running in the pod -func (k K8sClient) GetPodLogs(podName string, ns string) string { - req := k.K8sCS.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{}) - podLogs, err := req.Stream(context.TODO()) - if err != nil { - log.Fatal("err getting logs ", err) - } - defer podLogs.Close() - - buf := new(bytes.Buffer) - _, err = io.Copy(buf, podLogs) - if err != nil { - log.Fatal("err converting log buffer", err) - } - - return buf.String() -} - -// StartPodLogsStream starts stream of logs for the given pod -func (k K8sClient) StartPodLogsStream(podName string, ns string) { - req := k.K8sCS.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{}) - stream, err := req.Stream(context.TODO()) - if err != nil { - log.Fatal("err getting logs ", err) - } - defer stream.Close() - - fmt.Println("Waiting for the Pod Logs...") - - for { - buf := make([]byte, 2000) - numBytes, err := stream.Read(buf) - - // No newer logs - if numBytes == 0 { - // sleeping for 2 seconds to save CPU power of host running infinite loop - time.Sleep(2 * time.Second) - continue - } - - // Logs stream ended - if err == io.EOF { - break - } - - // Err fetching logs - if err != nil { - log.Fatal("stopping pod logs stream, err:", err) - } - - message := string(buf[:numBytes]) - fmt.Print(util.ColorText(message, util.Blue)) - } -} - // GetDeploymentList returns the deployment-list with a specific // label selector query func (k K8sClient) GetDeploymentList(labelSelector string) (*appsv1.DeploymentList, error) { diff --git a/pkg/client/lvmlocalpv.go b/pkg/client/lvmlocalpv.go index fc60c5b3..f2a0f0f7 100644 --- a/pkg/client/lvmlocalpv.go +++ b/pkg/client/lvmlocalpv.go @@ -113,7 +113,7 @@ func (k K8sClient) GetLVMNodes(lVols []string, rType util.ReturnType, labelSelec if lv, ok := lvsMap[name]; ok { list = append(list, lv) } else { - fmt.Printf("Error from server (NotFound): lvmvolume %s not found\n", name) + fmt.Printf("Error from server (NotFound): lvmnode %s not found\n", name) } } } diff --git a/pkg/clusterinfo/cluster-info.go b/pkg/clusterinfo/cluster-info.go index 027f56d1..4a259875 100644 --- a/pkg/clusterinfo/cluster-info.go +++ b/pkg/clusterinfo/cluster-info.go @@ -40,16 +40,7 @@ func compute(k *client.K8sClient) error { for casType, componentNames := range util.CasTypeToComponentNamesMap { componentDataMap, err := getComponentDataByComponents(k, componentNames, casType) if err == nil && len(componentDataMap) != 0 { - status, working := "", "" - if casType == util.LocalDeviceCasType { - var err error - status, working, err = getLocalPVDeviceStatus(componentDataMap) - if err != nil { - continue - } - } else { - status, working = getStatus(componentDataMap) - } + status, working := getStatus(componentDataMap) version := getVersion(componentDataMap) namespace := getNamespace(componentDataMap) clusterInfoRows = append( @@ -67,7 +58,6 @@ func compute(k *client.K8sClient) error { func getComponentDataByComponents(k *client.K8sClient, componentNames string, casType string) (map[string]util.ComponentData, error) { var podList *corev1.PodList - // Fetch Cstor Components componentDataMap := make(map[string]util.ComponentData) podList, _ = k.GetPods(fmt.Sprintf("openebs.io/component-name in (%s)", componentNames), "", "") if len(podList.Items) != 0 { @@ -92,25 +82,6 @@ func getComponentDataByComponents(k *client.K8sClient, componentNames string, ca } } - // Below is to handle corner cases in case of cstor and jiva, as they use components like ndm and localpv provisioner - // which are also used by other engine, below has been added to strictly identify the installed engine. - engineComponents := 0 - for key := range componentDataMap { - if !strings.Contains(util.NDMComponentNames, key) && strings.Contains(util.CasTypeToComponentNamesMap[casType], key) { - engineComponents++ - if casType == util.JivaCasType && key == util.HostpathComponentNames { - // Since hostpath component is not a unique engine component for jiva - engineComponents-- - } - } - } - if engineComponents == 0 { - return nil, fmt.Errorf("components for %s engine are not installed", casType) - } - - // The below is to fill in the expected components, for example if 5 out of 7 cstor components are there - // in the cluster, we would not be able what was the expected number of components, the below would ensure cstor - // needs 7 component always to work. for _, item := range strings.Split(componentNames, ",") { if _, ok := componentDataMap[item]; !ok { componentDataMap[item] = util.ComponentData{} @@ -139,21 +110,9 @@ func getStatus(componentDataMap map[string]util.ComponentData) (string, string) } } -func getLocalPVDeviceStatus(componentDataMap map[string]util.ComponentData) (string, string, error) { - if ndmData, ok := componentDataMap["ndm"]; ok { - if localPVData, ok := componentDataMap["openebs-localpv-provisioner"]; ok { - if ndmData.Namespace == localPVData.Namespace && localPVData.Namespace != "" && localPVData.CasType != "" { - status, working := getStatus(componentDataMap) - return status, working, nil - } - } - } - return "", "", fmt.Errorf("installed NDM is not for Device LocalPV") -} - func getVersion(componentDataMap map[string]util.ComponentData) string { - for key, val := range componentDataMap { - if !strings.Contains(util.NDMComponentNames, key) && val.Version != "" && val.Status == "Running" { + for _, val := range componentDataMap { + if val.Version != "" && val.Status == "Running" { return val.Version } } @@ -161,8 +120,8 @@ func getVersion(componentDataMap map[string]util.ComponentData) string { } func getNamespace(componentDataMap map[string]util.ComponentData) string { - for key, val := range componentDataMap { - if !strings.Contains(util.NDMComponentNames, key) && val.Namespace != "" { + for _, val := range componentDataMap { + if val.Namespace != "" { return val.Namespace } } diff --git a/pkg/clusterinfo/cluster-info_test.go b/pkg/clusterinfo/cluster-info_test.go deleted file mode 100644 index 3592e92b..00000000 --- a/pkg/clusterinfo/cluster-info_test.go +++ /dev/null @@ -1,602 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package clusterinfo - -import ( - "reflect" - "testing" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - "k8s.io/client-go/kubernetes/fake" -) - -func Test_getComponentDataByComponents(t *testing.T) { - type args struct { - k *client.K8sClient - componentNames string - casType string - } - tests := []struct { - name string - args args - want map[string]util.ComponentData - wantErr bool - }{ - { - "All components present and running", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cspcOperator, &cvcOperator, &cstorAdmissionWebhook, &ndm, &ndmOperator, &openebsCstorCsiController, &openebsCstorCsiNode), - }, - componentNames: util.CasTypeToComponentNamesMap[util.CstorCasType], - casType: util.CstorCasType, - }, - map[string]util.ComponentData{ - "cspc-operator": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "cvc-operator": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "cstor-admission-webhook": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "openebs-cstor-csi-node": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "openebs-cstor-csi-controller": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "ndm": {Namespace: "openebs", Status: "Running", Version: "1.1", CasType: "cstor"}, - "openebs-ndm-operator": {Namespace: "openebs", Status: "Running", Version: "1.1", CasType: "cstor"}, - }, - false, - }, - { - "Some components present and running", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cspcOperator, &cvcOperator, &cstorAdmissionWebhook, &ndmOperator, &openebsCstorCsiNode), - }, - componentNames: util.CasTypeToComponentNamesMap[util.CstorCasType], - casType: util.CstorCasType, - }, - map[string]util.ComponentData{ - "cspc-operator": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "cvc-operator": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "cstor-admission-webhook": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "openebs-cstor-csi-node": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "openebs-cstor-csi-controller": {}, - "ndm": {}, - "openebs-ndm-operator": {Namespace: "openebs", Status: "Running", Version: "1.1", CasType: "cstor"}, - }, - false, - }, - { - "All components present and running with some component having evicted pods", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cspcOperator, &cvcOperator, &cstorAdmissionWebhook, &ndm, &ndmOperator, &openebsCstorCsiController, &openebsCstorCsiNode, &cspcOperatorEvicted, &cvcOperatorEvicted), - }, - componentNames: util.CasTypeToComponentNamesMap[util.CstorCasType], - casType: util.CstorCasType, - }, - map[string]util.ComponentData{ - "cspc-operator": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "cvc-operator": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "cstor-admission-webhook": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "openebs-cstor-csi-node": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "openebs-cstor-csi-controller": {Namespace: "openebs", Status: "Running", Version: "2.1", CasType: "cstor"}, - "ndm": {Namespace: "openebs", Status: "Running", Version: "1.1", CasType: "cstor"}, - "openebs-ndm-operator": {Namespace: "openebs", Status: "Running", Version: "1.1", CasType: "cstor"}, - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getComponentDataByComponents(tt.args.k, tt.args.componentNames, tt.args.casType) - if (err != nil) != tt.wantErr { - t.Errorf("getComponentDataByComponents() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("getComponentDataByComponents() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getLocalPVDeviceStatus(t *testing.T) { - type args struct { - componentDataMap map[string]util.ComponentData - } - tests := []struct { - name string - args args - want string - want1 string - wantErr bool - }{ - { - "ndm and localpv provisioner in same ns", - args{ - componentDataMap: map[string]util.ComponentData{ - "openebs-localpv-provisioner": { - Namespace: "openebs", - Status: "Running", - Version: "1.1", - CasType: util.LocalDeviceCasType, - }, - "ndm": { - Namespace: "openebs", - Status: "Running", - Version: "3.1", - CasType: util.LocalDeviceCasType, - }, - }, - }, - "Healthy", - "2/2", - false, - }, - { - "ndm and localpv provisioner in same ns but ndm down", - args{ - componentDataMap: map[string]util.ComponentData{ - "openebs-localpv-provisioner": { - Namespace: "openebs", - Status: "Running", - Version: "1.1", - CasType: util.LocalDeviceCasType, - }, - "ndm": { - Namespace: "openebs", - Status: "Pending", - Version: "3.1", - CasType: util.LocalDeviceCasType, - }, - }, - }, - "Degraded", - "1/2", - false, - }, - { - "ndm and localpv provisioner in same ns but both down", - args{ - componentDataMap: map[string]util.ComponentData{ - "openebs-localpv-provisioner": { - Namespace: "openebs", - Status: "Pending", - Version: "1.1", - CasType: util.LocalDeviceCasType, - }, - "ndm": { - Namespace: "openebs", - Status: "Pending", - Version: "3.1", - CasType: util.LocalDeviceCasType, - }, - }, - }, - "Unhealthy", - "0/2", - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1, err := getLocalPVDeviceStatus(tt.args.componentDataMap) - if (err != nil) != tt.wantErr { - t.Errorf("getLocalPVDeviceStatus() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("getLocalPVDeviceStatus() got = %v, want %v", got, tt.want) - } - if got1 != tt.want1 { - t.Errorf("getLocalPVDeviceStatus() got1 = %v, want %v", got1, tt.want1) - } - }) - } -} - -func Test_getNamespace(t *testing.T) { - type args struct { - componentDataMap map[string]util.ComponentData - } - tests := []struct { - name string - args args - want string - }{ - { - "some running components with ndm in same ns", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "cstor", - Status: "Running", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Running", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "cstor", - }, - { - "some running components with ndm in different ns", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "openebs", - Status: "Running", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "cstor", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := getNamespace(tt.args.componentDataMap); got != tt.want { - t.Errorf("getNamespace() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getStatus(t *testing.T) { - type args struct { - componentDataMap map[string]util.ComponentData - } - tests := []struct { - name string - args args - want string - want1 string - }{ - { - "some running components", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "cstor", - Status: "Running", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Running", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "Degraded", - "2/4", - }, - { - "No running components", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "cstor", - Status: "Pending", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "Unhealthy", - "0/4", - }, - { - "All running components", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Running", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "cstor", - Status: "Running", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Running", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Running", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "Healthy", - "4/4", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1 := getStatus(tt.args.componentDataMap) - if got != tt.want { - t.Errorf("getStatus() got = %v, want %v", got, tt.want) - } - if got1 != tt.want1 { - t.Errorf("getStatus() got1 = %v, want %v", got1, tt.want1) - } - }) - } -} - -func Test_getVersion(t *testing.T) { - type args struct { - componentDataMap map[string]util.ComponentData - } - tests := []struct { - name string - args args - want string - }{ - { - "some running components", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "cstor", - Status: "Running", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Running", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "1.1", - }, - { - "No running components except ndm", - args{ - componentDataMap: map[string]util.ComponentData{ - "cstor-csi-controller": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "ndm": { - Namespace: "cstor", - Status: "Running", - Version: "3.1", - CasType: "cstor", - }, - "cstor-operator": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - "cstor-some-xyz-component": { - Namespace: "cstor", - Status: "Pending", - Version: "1.1", - CasType: "cstor", - }, - }, - }, - "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := getVersion(tt.args.componentDataMap); got != tt.want { - t.Errorf("getVersion() = %v, want %v", got, tt.want) - } - }) - } -} - -//map[cspc-operator:{openebs Running 2.1 cstor} cstor-admission-webhook:{openebs Running 2.1 cstor} cvc-operator:{openebs Running 2.1 cstor} ndm:{openebs Running 1.1 cstor} openebs-cstor-csi-controller:{openebs Running 2.1 cstor} openebs-cstor-csi-node:{openebs Running 2.1 cstor} openebs-ndm-operator:{openebs Running 1.1 cstor}], -//map[cspc-operator:{openebs Running 2.1 cstor} cstor-admission-webhook:{openebs Running 2.1 cstor} cvc-operator:{openebs Running 2.1 cstor} ndm:{openebs Running 1.1 cstor} ndm-operator:{openebs Running 1.1 cstor} openebs-cstor-csi-controller:{openebs Running 2.1 cstor} openebs-cstor-csi-node:{openebs Running 2.1 cstor}] - -func Test_compute(t *testing.T) { - type args struct { - k *client.K8sClient - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "All components of cstor present and running", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cspcOperator, &cvcOperator, &cstorAdmissionWebhook, &ndm, &ndmOperator, &openebsCstorCsiController, &openebsCstorCsiNode), - }, - }, - false, - }, - { - "Some components of cstor present and running", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cspcOperator, &cvcOperator, &cstorAdmissionWebhook, &ndmOperator, &openebsCstorCsiNode), - }, - }, - false, - }, - { - "All components of cstor present and running with some component having evicted pods", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cspcOperator, &cvcOperator, &cstorAdmissionWebhook, &ndm, &ndmOperator, &openebsCstorCsiController, &openebsCstorCsiNode, &cspcOperatorEvicted, &cvcOperatorEvicted), - }, - }, - false, - }, - { - "If no components are present", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(), - }, - }, - true, - }, - { - "If ndm and localpv provisioner components are in same ns", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&localpvProvisionerInOpenebs, &ndm, &ndmOperator), - }, - }, - false, - }, - { - "If ndm and localpv provisioner components are in different ns", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&localpvProvisioner, &ndm, &ndmOperator), - }, - }, - false, - }, - { - "If jiva and ndm in same ns", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&jivaOperator, &openebsJivaCsiController, &openebsJivaCsiNode, &ndm, &ndmOperator, &localpvProvisionerInOpenebs), - }, - }, - false, - }, - { - "If jiva and ndm in different ns", - args{ - k: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&jivaOperator, &openebsJivaCsiController, &openebsJivaCsiNode, &ndmXYZ, &ndmOperatorXYZ, &localpvProvisionerInOpenebs), - }, - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := compute(tt.args.k); (err != nil) != tt.wantErr { - t.Errorf("compute() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/clusterinfo/testdata_test.go b/pkg/clusterinfo/testdata_test.go deleted file mode 100644 index 87f1fd8c..00000000 --- a/pkg/clusterinfo/testdata_test.go +++ /dev/null @@ -1,200 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package clusterinfo - -import ( - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var cspcOperator = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspcOperatorPOD", - Namespace: "openebs", - UID: "some-uuid-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "cspc-operator", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var cspcOperatorEvicted = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspcOperatorEvictedPOD", - Namespace: "openebs", - UID: "some-uuid-8", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "cspc-operator", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Evicted"}, -} - -var cvcOperator = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cvcOperatorPOD", - Namespace: "openebs", - UID: "some-uuid-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "cvc-operator", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var cvcOperatorEvicted = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cvcOperatorEvictedPOD", - Namespace: "openebs", - UID: "some-uuid-9", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "cvc-operator", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Evicted"}, -} - -var cstorAdmissionWebhook = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstorAdmissionWebhookPOD", - Namespace: "openebs", - UID: "some-uuid-3", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "cstor-admission-webhook", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var openebsCstorCsiNode = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "openebsCstorCsiNodePOD", - Namespace: "openebs", - UID: "some-uuid-4", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-cstor-csi-node", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var openebsCstorCsiController = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "openebsCstorCsiControllerPOD", - Namespace: "openebs", - UID: "some-uuid-5", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-cstor-csi-controller", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var ndm = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ndmPOD", - Namespace: "openebs", - UID: "some-uuid-6", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "ndm", "openebs.io/version": "1.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var ndmOperator = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ndmOperatorPOD", - Namespace: "openebs", - UID: "some-uuid-7", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-ndm-operator", "openebs.io/version": "1.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var localpvProvisionerInOpenebs = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "localpvprovisionerInOpenebsPOD", - Namespace: "openebs", - UID: "some-uuid-10", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-localpv-provisioner", "openebs.io/version": "1.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var localpvProvisioner = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "localpvprovisionerPOD", - Namespace: "xyz", - UID: "some-uuid-10", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-localpv-provisioner", "openebs.io/version": "1.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var openebsJivaCsiNode = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "openebsJivaCsiNodePOD", - Namespace: "openebs", - UID: "some-uuid-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-jiva-csi-node", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var jivaOperator = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "jivaOperatorPOD", - Namespace: "openebs", - UID: "some-uuid-8", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "jiva-operator", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var openebsJivaCsiController = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "openebsJivaCsiControllerPOD", - Namespace: "openebs", - UID: "some-uuid-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-jiva-csi-controller", "openebs.io/version": "2.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var ndmXYZ = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ndmPOD", - Namespace: "xyz", - UID: "some-uuid-6", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "ndm", "openebs.io/version": "1.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} - -var ndmOperatorXYZ = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ndmOperatorPOD", - Namespace: "xyz", - UID: "some-uuid-7", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/component-name": "openebs-ndm-operator", "openebs.io/version": "1.1"}, - }, - Status: corev1.PodStatus{Phase: "Running"}, -} diff --git a/pkg/generate/cspc.go b/pkg/generate/cspc.go deleted file mode 100644 index b0515675..00000000 --- a/pkg/generate/cspc.go +++ /dev/null @@ -1,299 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package generate - -import ( - "fmt" - "sort" - "strconv" - "strings" - - "github.com/ghodss/yaml" - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// isPoolValid checks if a CStor pool is valid -func isPoolTypeValid(raid string) bool { - if raid == "stripe" || raid == "mirror" || raid == "raidz" || raid == "raidz2" { - return true - } - return false -} - -// CSPC calls the generate routine for different cas-types -func CSPC(nodes []string, devs int, raid, capacity string) error { - c := client.NewK8sClient() - if !isPoolTypeValid(strings.ToLower(raid)) { - // TODO: Use the well defined pool constant types from openebs/api when added there - return fmt.Errorf("invalid pool type %s", raid) - } - // resource.Quantity doesn't like the bits or bytes suffixes - capacity = strings.Replace(capacity, "b", "", 1) - capacity = strings.Replace(capacity, "B", "", 1) - size, err := resource.ParseQuantity(capacity) - if err != nil { - return err - } - _, str, err := cspc(c, nodes, devs, strings.ToLower(raid), size) - if err != nil { - return err - } - fmt.Println(str) - return nil -} - -// cspc takes eligible nodes, number of devices and poolType to create a pool cluster template -func cspc(c *client.K8sClient, nodes []string, devs int, poolType string, minSize resource.Quantity) (*cstorv1.CStorPoolCluster, string, error) { - // 0. Figure out the OPENEBS_NAMESPACE for CStor - cstorNS, err := c.GetOpenEBSNamespace(util.CstorCasType) - // assume CSTOR's OPENEBS_NAMESPACE has all the relevant blockdevices - c.Ns = cstorNS - if err != nil { - return nil, "", fmt.Errorf("unable to determine the cStor namespace error: %v", err) - } - // 0.1 Validate user input, check if user hasn't entered less than 64Mi - cstorMin := resource.MustParse("64Mi") - if minSize.Cmp(cstorMin) < 0 { - return nil, "", fmt.Errorf("minimum size of supported block-devices in a cspc is 64Mi") - } - // 0.2 Validate user input, check if user has entered >= minimum supported BD-count - if min := minCount()[poolType]; devs < min { - return nil, "", fmt.Errorf("%s pool requires a minimum of %d block device per node", - poolType, min) - } - // 1. Validate nodes & poolType, fetch disks - nodeList, err := c.GetNodes(nodes, "", "") - if err != nil { - return nil, "", fmt.Errorf("(server error) unable to fetch node information %s", err) - } - if len(nodeList.Items) != len(nodes) { - return nil, "", fmt.Errorf("not all worker nodes are available for provisioning a cspc") - } - // 1.1 Translate nodeNames to node's hostNames to fetch disks - // while they might seem equivalent, they aren't equal, this quirk is - // visible clearly for EKS clusters - var hostnames []string - for _, node := range nodeList.Items { - // I hope it is unlikely for a K8s node to have an empty hostname - hostnames = append(hostnames, node.Labels["kubernetes.io/hostname"]) - } - // 2. Fetch BD's from the eligible/valid nodes by hostname labels - bds, err := c.GetBDs(nil, "kubernetes.io/hostname in ("+strings.Join(hostnames, ",")+")") - if err != nil || len(bds.Items) == 0 { - return nil, "", fmt.Errorf("no blockdevices found in nodes with %v hostnames", hostnames) - } - _, err = filterCStorCompatible(bds, minSize) - if err != nil { - return nil, "", fmt.Errorf("(server error) unable to fetch bds from %v nodes", nodes) - } - // 3. Choose devices at the valid BDs by hostname - hostToBD := make(map[string][]v1alpha1.BlockDevice) - for _, bd := range bds.Items { - hostToBD[bd.Labels["kubernetes.io/hostname"]] = append(hostToBD[bd.Labels["kubernetes.io/hostname"]], bd) - } - // 4. Select disks and create the PoolSpec - p, err := makePools(poolType, devs, hostToBD, nodes, hostnames, minSize) - if err != nil { - return nil, "", err - } - - // 5. Write the cspc object with a dummy name - cspc := cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{Namespace: cstorNS, GenerateName: "cstor"}, - Spec: cstorv1.CStorPoolClusterSpec{ - Pools: *p, - }, - } - // 6. Unmarshall it into a string - y, err := yaml.Marshal(cspc) - if err != nil { - fmt.Printf("err: %v\n", err) - return nil, "", err - } - specStr := string(y) - // 7. removing status and versionDetails field - specStr = specStr[:strings.Index(specStr, "status: {}")] - // 8. Split the string by the newlines/carriage returns and insert the BD's link - specStr = addBDDetailComments(specStr, bds) - return &cspc, specStr, nil -} - -// addBDDetailComments adds more information about the blockdevice in a cspc YAML string -func addBDDetailComments(yaml string, bdList *v1alpha1.BlockDeviceList) string { - finalYaml := "" - for _, l := range strings.Split(yaml, "\n") { - if strings.Contains(l, "- blockDeviceName:") { - name := strings.Trim(strings.Split(l, ":")[1], " ") - finalYaml = finalYaml + getBDComment(name, bdList) + "\n" - } - finalYaml = finalYaml + l + "\n" - } - return finalYaml -} - -// getBDComment returns information about a blockdevice, with fixed whitespace -// to match the identation level -func getBDComment(name string, bdList *v1alpha1.BlockDeviceList) string { - for _, bd := range bdList.Items { - if bd.Name == name { - return " # " + bd.Spec.Path + " " + util.ConvertToIBytes(strconv.FormatUint(bd.Spec.Capacity.Storage, 10)) - } - } - return "" -} - -// makePools creates a poolSpec based on the poolType, number of devices per -// pool instance and a collection of blockdevices by nodes -func makePools(poolType string, nDevices int, bd map[string][]v1alpha1.BlockDevice, - nodes []string, hosts []string, minsize resource.Quantity) (*[]cstorv1.PoolSpec, error) { - // IMPORTANT: User is more likely to see the nodeNames, so the errors - // should preferably be shown in terms of nodeNames and not hostNames - var spec []cstorv1.PoolSpec - switch poolType { - case string(cstorv1.PoolStriped): - // always single RAID-group with nDevices patched together, cannot disk replace, - // no redundancy in a pool, redundancy possible across pool instances - - // for each eligible set of BDs from each eligible nodes with hostname - // "host", take nDevices number of BDs - for i, host := range hosts { - bds, ok := bd[host] - if !ok { - // DOUBT: Do 0 or lesser number of BDs demand a separate error string? - // I can ask to create a stripe pool with 1 disk and my - // choice of node might not have eligible BDs - return nil, fmt.Errorf("no eligible blockdevices found in node %s", nodes[i]) - } - if len(bds) < nDevices { - // the node might have lesser number of BDs - return nil, fmt.Errorf("not enough blockdevices found on node %s, want %d, found %d", nodes[i], nDevices, len(bds)) - } - var raids []cstorv1.CStorPoolInstanceBlockDevice - for d := 0; d < nDevices; d++ { - raids = append(raids, cstorv1.CStorPoolInstanceBlockDevice{BlockDeviceName: bds[d].Name}) - } - spec = append(spec, cstorv1.PoolSpec{ - NodeSelector: map[string]string{"kubernetes.io/hostname": host}, - DataRaidGroups: []cstorv1.RaidGroup{{CStorPoolInstanceBlockDevices: raids}}, - PoolConfig: cstorv1.PoolConfig{ - DataRaidGroupType: string(cstorv1.PoolStriped), - }, - }) - } - return &spec, nil - case string(cstorv1.PoolMirrored), string(cstorv1.PoolRaidz), string(cstorv1.PoolRaidz2): - min := minCount()[poolType] - if nDevices%min != 0 { - // there must be min number of devices per RaidGroup - return nil, fmt.Errorf("number of devices must be a multiple of %d", min) - } - if min > nDevices { - return nil, fmt.Errorf("insufficient blockdevices require minimum %d devices for %s", min, poolType) - } - // 1. Start filling in the devices in their RAID-groups per the hostnames - for i, host := range hosts { - var raidGroups []cstorv1.RaidGroup - // add all BDs to a CSPCs CSPI spec - bds := bd[host] - if len(bds) < nDevices { - return nil, fmt.Errorf("not enough eligible blockdevices found on node %s, want %d, found %d", nodes[i], nDevices, len(bds)) - } - // 1. sort the BDs by increasing order - sort.Slice(bds, func(i, j int) bool { - // sort by increasing order - return bds[i].Spec.Capacity.Storage < bds[j].Spec.Capacity.Storage - }) - // 2. Check if close to the desired capacity of the pool can be achieved by minimising disk wastage - // 3. Suggest the start and end index for the BDs to be used for the raid group - maxIndex := len(bds) - if maxIndex < nDevices { - return nil, fmt.Errorf("not enough eligible blockdevices found on node %s, want %d, found %d", nodes[i], min, maxIndex) - } - devices := Generate(v1alpha1.BlockDeviceList{Items: bds}) - for d := 0; d < nDevices/min; d++ { - var raids []cstorv1.CStorPoolInstanceBlockDevice - d, thisRaidGroup, err := devices.Select(minsize, min) - // re-assign the head node of the linked-list for next iteration - // pinning the new head to the variable declared above for upcoming usage as required - devices = d - if err != nil { - return nil, err - } - for j := 0; j < min; j++ { - // each RaidGroup has min number of devices - raids = append(raids, cstorv1.CStorPoolInstanceBlockDevice{BlockDeviceName: thisRaidGroup[j].ObjectMeta.Name}) - } - raidGroups = append(raidGroups, cstorv1.RaidGroup{CStorPoolInstanceBlockDevices: raids}) - } - // add the CSPI BD spec inside cspc to a PoolSpec - spec = append(spec, cstorv1.PoolSpec{ - NodeSelector: map[string]string{"kubernetes.io/hostname": host}, - DataRaidGroups: raidGroups, - PoolConfig: cstorv1.PoolConfig{ - DataRaidGroupType: poolType, - }, - }) - } - return &spec, nil - default: - return nil, fmt.Errorf("unknown pool-type %s", poolType) - } -} - -// minCount states the minimum number of BDs for a pool type in a RAID-group -// this is an example of an immutable map -func minCount() map[string]int { - return map[string]int{ - string(cstorv1.PoolStriped): 1, - // mirror: data is mirrored across even no of disks - string(cstorv1.PoolMirrored): 2, - // raidz: data is spread across even no of disks and one disk is for parity^ - // ^recovery information, metadata, etc - // can handle one device failing - string(cstorv1.PoolRaidz): 3, - // raidz2: data is spread across even no of disks and two disks are for parity - // can handle two devices failing - string(cstorv1.PoolRaidz2): 6, - } -} - -// filterCStorCompatible takes a list of BDs and gives out a list of BDs which can be used to provision a pool -func filterCStorCompatible(bds *v1alpha1.BlockDeviceList, minLimit resource.Quantity) (*v1alpha1.BlockDeviceList, error) { - // TODO: Optionally reject sparse-disks depending on configs - var final []v1alpha1.BlockDevice - for _, bd := range bds.Items { - // an eligible blockdevice is in active+unclaimed state and lacks a file-system - if bd.Status.State == v1alpha1.BlockDeviceActive && - bd.Status.ClaimState == v1alpha1.BlockDeviceUnclaimed && - bd.Spec.FileSystem.Type == "" && - // BD's capacity >=64 MiB - bd.Spec.Capacity.Storage >= uint64(minLimit.Value()) { - final = append(final, bd) - } - } - bds.Items = final - if len(final) == 0 { - return nil, fmt.Errorf("found no eligble blockdevices of size %s", minLimit.String()) - } - return bds, nil -} diff --git a/pkg/generate/cspc_test.go b/pkg/generate/cspc_test.go deleted file mode 100644 index cf1561a7..00000000 --- a/pkg/generate/cspc_test.go +++ /dev/null @@ -1,248 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package generate - -import ( - "testing" - - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - cstorfake "github.com/openebs/api/v2/pkg/client/clientset/versioned/fake" - "github.com/openebs/openebsctl/pkg/client" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/client-go/kubernetes/fake" -) - -func TestCSPC(t *testing.T) { - type args struct { - c *client.K8sClient - nodes []string - devs int - GB int - poolType string - } - - tests := []struct { - name string - args args - want *cstorv1.CStorPoolCluster - str string - wantErr bool - }{ - { - "no cstor installation present", - args{ - c: &client.K8sClient{Ns: "", K8sCS: fake.NewSimpleClientset(), OpenebsCS: cstorfake.NewSimpleClientset()}, - nodes: []string{"node1"}, devs: 1, poolType: ""}, nil, - "", true, - }, - { - "cstor present, no suggested nodes present", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod), OpenebsCS: cstorfake.NewSimpleClientset()}, - nodes: []string{"node1"}, devs: 1, poolType: ""}, nil, - "", true, - }, - { - "cstor present, suggested nodes present, blockdevices absent", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1), OpenebsCS: cstorfake.NewSimpleClientset()}, - nodes: []string{"node1"}, devs: 1, poolType: ""}, nil, - "", true, - }, - { - "cstor present, suggested nodes present, blockdevices present but incompatible", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1), - OpenebsCS: cstorfake.NewSimpleClientset(&activeBDwEXT4, &inactiveBDwEXT4)}, - nodes: []string{"node1"}, devs: 1, poolType: ""}, nil, - "", true, - }, - { - "cstor present, suggested nodes present, blockdevices present and compatible", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1), - OpenebsCS: cstorfake.NewSimpleClientset(&activeUnclaimedUnforattedBD)}, - nodes: []string{"node1"}, devs: 1, poolType: "stripe"}, &cspc1Struct, cspc1, false, - }, - { - "all good config, CSTOR_NAMESPACE is correctly identified each time", - args{ - c: &client.K8sClient{Ns: "randomNamespaceWillGetReplaced", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1), - OpenebsCS: cstorfake.NewSimpleClientset(&activeUnclaimedUnforattedBD)}, - nodes: []string{"node1"}, devs: 1, poolType: "stripe"}, &cspc1Struct, cspc1, false, - }, - { - "all good config, 2 disk stripe pool for 3 nodes", - args{ - c: &client.K8sClient{Ns: "", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1, &node2, &node3), - OpenebsCS: cstorfake.NewSimpleClientset(&goodBD1N1, &goodBD1N2, &goodBD1N3, &goodBD2N1, &goodBD2N2, &goodBD2N3)}, - // Stripe pools can have only one RaidGroup per instance, i.e. - nodes: []string{"node1", "node2", "node3"}, devs: 2, poolType: "stripe"}, &threeNodeTwoDevCSPC, StripeThreeNodeTwoDev, false, - }, - { - "good config, no BDs", - args{ - c: &client.K8sClient{Ns: "randomNamespaceWillGetReplaced", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1), - OpenebsCS: cstorfake.NewSimpleClientset(&inactiveBDwEXT4)}, - nodes: []string{"node1"}, devs: 5, poolType: "stripe"}, nil, "", true, - }, - { - "all good mirror pool gets provisioned, 2 nodes of same size on 3 nodes", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1, &node2, &node3), - OpenebsCS: cstorfake.NewSimpleClientset(&goodBD1N1, &goodBD2N1, &goodBD1N2, &goodBD2N2, &goodBD1N3, &goodBD2N3)}, - nodes: []string{"node1", "node2", "node3"}, devs: 2, poolType: "mirror"}, &mirrorCSPC, mirrorCSPCstr, false, - }, - { - "all good raidz pool gets provisioned, 3 nodes of same size on 2 nodes", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1, &node2), - OpenebsCS: cstorfake.NewSimpleClientset(&goodBD1N1, &goodBD2N1, &goodBD3N1, &goodBD1N2, &goodBD2N2, &goodBD3N2)}, - nodes: []string{"node1", "node2"}, devs: 3, poolType: "raidz"}, &raidzCSPCThreeBDTwoNode, raidzCSPCstr, false, - }, - { - "all good raidz2 pool does not gets provisioned, insufficient BDs 3 nodes of same size on 2 nodes", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1, &node2), - OpenebsCS: cstorfake.NewSimpleClientset(&goodBD1N1, - &goodBD2N1, &goodBD3N1, &goodBD4N1, &goodBD1N2, - &goodBD2N2, &goodBD3N2, &goodBD4N2)}, - nodes: []string{"node1", "node2"}, devs: 3, poolType: "raidz2"}, nil, "", true, - }, - { - "raidz2 pool provisioned, 2 nodes, 6 BDs", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1, &node2), - OpenebsCS: cstorfake.NewSimpleClientset(&goodBD1N1, - &goodBD2N1, &goodBD3N1, &goodBD4N1, &goodBD5N1, - &goodBD6N1, &goodBD1N2, &goodBD2N2, &goodBD3N2, - &goodBD4N2, &goodBD5N2, &goodBD6N2)}, - nodes: []string{"node1", "node2"}, devs: 6, poolType: "raidz2"}, &raidz2CSPCSixBDTwoNode, raidz2CSPCstr, false, - }, - { - "raidz2 not provisioned, requires 2 more BDs", - args{ - c: &client.K8sClient{Ns: "openebs", K8sCS: fake.NewSimpleClientset(&cstorCSIpod, &node1, &node2), - OpenebsCS: cstorfake.NewSimpleClientset(&goodBD1N1, - &goodBD2N1, &goodBD3N1, &goodBD4N1, &goodBD1N2, - &goodBD2N2, &goodBD3N2, &goodBD4N2)}, - nodes: []string{"node1", "node2"}, devs: 4, poolType: "raidz2"}, nil, "", true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // tt.args.GB, - got, got1, err := cspc(tt.args.c, tt.args.nodes, tt.args.devs, tt.args.poolType, resource.MustParse("1Gi")) - if (err != nil) != tt.wantErr { - t.Errorf("cspc() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.YAMLEq(t, tt.str, got1, "stringified YAML is not the same as expected") - assert.Exactlyf(t, got, tt.want, "struct is not same") - }) - } -} - -func Test_isPoolTypeValid(t *testing.T) { - tests := []struct { - name string - poolNames []string - want bool - }{ - {name: "valid pools", poolNames: []string{"stripe", "mirror", "raidz", "raidz2"}, want: true}, - {name: "invalid pools", poolNames: []string{"striped", "mirrored", "raid-z", "raid-z2", "lvm", "raidz1", "raidz0"}, want: false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - for _, poolType := range tt.poolNames { - if got := isPoolTypeValid(poolType); got != tt.want { - t.Errorf("isPoolTypeValid() = %v, want %v", got, tt.want) - } - } - }) - } -} - -func Test_makePools(t *testing.T) { - type args struct { - poolType string - nDevices int - bd map[string][]v1alpha1.BlockDevice - nodes []string - hosts []string - minSize resource.Quantity - } - tests := []struct { - name string - args args - want *[]cstorv1.PoolSpec - wantErr bool - }{ - {"stripe, three node, two disks", args{"stripe", 2, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1}, - "node2": {goodBD1N2, goodBD2N2}, "node3": {goodBD1N3, goodBD2N3}}, - []string{"node1", "node2", "node3"}, []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, &threeNodeTwoDevCSPC.Spec.Pools, false}, - {"stripe, three node, two disks, one node lacking disks", args{"stripe", 2, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1}, - "node2": {goodBD1N2, goodBD2N2}}, - []string{"node1", "node2", "node3"}, []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, nil, true}, - {"stripe, three node, two disks, one node lacking required disks", args{"stripe", 2, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1}, - "node2": {goodBD1N2}, "node3": {goodBD1N3, goodBD2N2}}, []string{"node1", "node2", "node3"}, - []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, nil, true}, - {"raidz, three node, three disks but only two disks present in node3", args{"raidz", 3, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1, goodBD3N1}, - "node2": {goodBD1N2, goodBD2N2, goodBD3N2}, "node3": {goodBD1N3, goodBD2N3}}, - []string{"node1", "node2", "node3"}, []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, nil, true}, - {"unknown pool, three node, two disks", args{"randompoolwhichmakesnosense", 2, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1}, - "node2": {goodBD1N2, goodBD2N2}, "node3": {goodBD1N3, goodBD2N3}}, - []string{"node1", "node2", "node3"}, []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, nil, true}, - {"mirror, three node, two disks", args{"mirror", 2, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1}, - "node2": {goodBD1N2, goodBD2N2}, "node3": {goodBD1N3, goodBD2N3}}, - []string{"node1", "node2", "node3"}, []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, &mirrorCSPC.Spec.Pools, false}, - {"mirror, two node, four disks", args{"mirror", 4, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1}, - "node2": {goodBD1N2, goodBD2N2, goodBD3N2, goodBD4N2}, "node3": {goodBD1N3, goodBD2N3}}, - // in the above example, presence of node3 BDs don't matter - []string{"node1", "node2"}, []string{"node1", "node2"}, resource.MustParse("1Gi")}, &mirrorCSPCFourBDs.Spec.Pools, false}, - {"mirror, three node, one disk", args{"mirror", 1, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1}, - "node2": {goodBD1N2, goodBD2N2}, "node3": {goodBD1N3, goodBD2N3}}, - // one cannot create a mirror pool with just one disk per node - []string{"node1", "node2", "node3"}, []string{"node1", "node2", "node3"}, resource.MustParse("1Gi")}, nil, true}, - {"raidz, two node, three disk", args{"raidz", 3, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1, goodBD3N1}, "node2": {goodBD1N2, goodBD2N2, goodBD3N2}}, - []string{"node1", "node2"}, []string{"node1", "node2"}, resource.MustParse("1Gi")}, &raidzCSPCThreeBDTwoNode.Spec.Pools, false}, - {"raidz2, two node, three disk", args{"raidz2", 6, - map[string][]v1alpha1.BlockDevice{"node1": {goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1, goodBD5N1, goodBD6N1}, "node2": {goodBD1N2, goodBD2N2, goodBD3N2, goodBD4N2, goodBD5N2, goodBD6N2}}, - []string{"node1", "node2"}, []string{"node1", "node2"}, resource.MustParse("1Gi")}, &raidz2CSPCSixBDTwoNode.Spec.Pools, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := makePools(tt.args.poolType, tt.args.nDevices, tt.args.bd, tt.args.nodes, tt.args.hosts, tt.args.minSize) - if (err != nil) != tt.wantErr { - t.Errorf("makePools() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.Equal(t, tt.want, got, "pool specs differ for %s", tt.name) - }) - } -} diff --git a/pkg/generate/sort.go b/pkg/generate/sort.go deleted file mode 100644 index c68819f1..00000000 --- a/pkg/generate/sort.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package generate - -import ( - "fmt" - - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - "k8s.io/apimachinery/pkg/api/resource" -) - -// DeviceList is a LinkedList of BlockDevices -type DeviceList struct { - item *v1alpha1.BlockDevice - next *DeviceList -} - -// New returns a new DeviceList node with a bd device -func New(bd v1alpha1.BlockDevice) *DeviceList { - return &DeviceList{&bd, nil} -} - -// Generate returns a new initialized *DeviceList(linked list) with the list of Blockdevices -func Generate(list v1alpha1.BlockDeviceList) *DeviceList { - if len(list.Items) == 0 { - return nil - } - var head *DeviceList - curr := head - for _, bd := range list.Items { - if curr == nil { - head = New(bd) - curr = head - } else { - curr.next = New(bd) - curr = curr.next - } - } - return head -} - -// Select returns count number of Blockdevices from the DeviceList LinkedList -func (head *DeviceList) Select(size resource.Quantity, count int) (*DeviceList, []v1alpha1.BlockDevice, error) { - if count == 1 { - // there's only one way of selecting one disk such that losses are - // minimized in a single RaidGroup - curr := head - head = head.next - return head, []v1alpha1.BlockDevice{*curr.item}, nil - } - curr := head - fakeHead := &DeviceList{item: &v1alpha1.BlockDevice{}, next: head} - prev := fakeHead - results := []v1alpha1.BlockDevice{} - // ahead is count nodes ahead of curr - ahead := head - for i := 1; i < count; i++ { - if ahead == nil { - return head, nil, fmt.Errorf("wanted %d blockdevices, have %d to pick", count, i) - } - ahead = ahead.next - } - for ahead != nil { - capFirst := resource.MustParse(fmt.Sprintf("%d", curr.item.Spec.Capacity.Storage)) - capLast := resource.MustParse(fmt.Sprintf("%d", ahead.item.Spec.Capacity.Storage)) - if capFirst.Cmp(capLast) == 0 { - // add all the devices in the same group - for curr != ahead { - results = append(results, *curr.item) - curr = curr.next - } - results = append(results, *curr.item) - // 1. Remove the set of BDs from the LinkedList - prev.next = ahead.next - if len(results) == count { - break - } - } - prev = curr - curr = curr.next - ahead = ahead.next - } - head = fakeHead.next - if len(results) != count { - return head, nil, fmt.Errorf("wanted %d blockdevices, have %d to pick", count, len(results)) - } - return head, results, nil -} diff --git a/pkg/generate/sort_test.go b/pkg/generate/sort_test.go deleted file mode 100644 index 8137b90a..00000000 --- a/pkg/generate/sort_test.go +++ /dev/null @@ -1,203 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package generate - -import ( - "fmt" - "reflect" - "testing" - - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - "github.com/openebs/api/v2/pkg/apis/types" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestGenerate(t *testing.T) { - type args struct { - list v1alpha1.BlockDeviceList - } - tests := []struct { - name string - args args - want *DeviceList - }{ - {"empty node LinkedList", - args{list: v1alpha1.BlockDeviceList{Items: []v1alpha1.BlockDevice{}}}, nil}, - {"single node LinkedList", - args{list: v1alpha1.BlockDeviceList{Items: []v1alpha1.BlockDevice{goodBD1N1}}}, - &DeviceList{&goodBD1N1, nil}, - }, - { - "two node LinkedList", - args{list: v1alpha1.BlockDeviceList{Items: []v1alpha1.BlockDevice{goodBD1N1, goodBD1N2}}}, - &DeviceList{&goodBD1N1, &DeviceList{&goodBD1N2, nil}}, - }, - { - "four node LinkedList", - args{list: v1alpha1.BlockDeviceList{Items: []v1alpha1.BlockDevice{goodBD1N1, goodBD1N2, goodBD1N3, goodBD2N1}}}, - &DeviceList{&goodBD1N1, &DeviceList{&goodBD1N2, &DeviceList{&goodBD1N3, - &DeviceList{&goodBD2N1, nil}}}}, - }, - { - "five node LinkedList", - args{list: v1alpha1.BlockDeviceList{Items: []v1alpha1.BlockDevice{goodBD1N1, goodBD1N2, goodBD1N3, goodBD2N1, goodBD3N1}}}, - &DeviceList{&goodBD1N1, &DeviceList{&goodBD1N2, &DeviceList{&goodBD1N3, - &DeviceList{&goodBD2N1, &DeviceList{&goodBD3N1, nil}}}}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, Generate(tt.args.list), "Generate(%v)", tt.args.list) - }) - } -} - -func TestDeviceList_Select(t *testing.T) { - type args struct { - head *DeviceList - size resource.Quantity - count int - } - tests := []struct { - name string - args args - want []v1alpha1.BlockDevice - wantErr bool - }{ - {"one node LinkedList", args{&DeviceList{&goodBD1N1, nil}, resource.MustParse("0Ki"), 1}, []v1alpha1.BlockDevice{goodBD1N1}, false}, - {"single node LinkedList", args{&DeviceList{&goodBD1N1, nil}, resource.MustParse("1Gi"), 1}, - []v1alpha1.BlockDevice{goodBD1N1}, false}, - {"two node LinkedList, one BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, nil}}, - resource.MustParse("1Gi"), 1}, []v1alpha1.BlockDevice{goodBD1N1}, false}, - {"two node LinkedList, four BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, nil}}, - resource.MustParse("1Gi"), 4}, nil, true}, - {"two node LinkedList, two BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, nil}}, - resource.MustParse("1Gi"), 2}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1}, false}, - {"three node LinkedList, one BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, &DeviceList{&goodBD3N1, nil}}}, - resource.MustParse("1Gi"), 1}, []v1alpha1.BlockDevice{goodBD1N1}, false}, - {"three node LinkedList, two BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, &DeviceList{&goodBD3N1, nil}}}, - resource.MustParse("1Gi"), 2}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1}, false}, - {"three node LinkedList, three BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, &DeviceList{&goodBD3N1, nil}}}, - resource.MustParse("1Gi"), 3}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1}, false}, - {"four node LinkedList, four BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, - &DeviceList{&goodBD3N1, &DeviceList{&goodBD4N1, nil}}}}, - resource.MustParse("1Gi"), 4}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1}, false}, - {"four node LinkedList, three BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, - &DeviceList{&goodBD3N1, &DeviceList{&goodBD4N1, nil}}}}, - resource.MustParse("1Gi"), 3}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1}, false}, - {"five node LinkedList, five BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, - &DeviceList{&goodBD3N1, &DeviceList{&goodBD4N1, &DeviceList{&goodBD5N1, nil}}}}}, - resource.MustParse("1Gi"), 5}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1, goodBD5N1}, false}, - {"six node LinkedList, four BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, - &DeviceList{&goodBD3N1, &DeviceList{&goodBD4N1, &DeviceList{&goodBD5N1, &DeviceList{&goodBD6N1, nil}}}}}}, - resource.MustParse("1Gi"), 4}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1}, false}, - {"six node LinkedList, five BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, - &DeviceList{&goodBD3N1, &DeviceList{&goodBD4N1, &DeviceList{&goodBD5N1, &DeviceList{&goodBD6N1, nil}}}}}}, - resource.MustParse("1Gi"), 5}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1, goodBD5N1}, false}, - {"six node LinkedList, six BD required", args{&DeviceList{&goodBD1N1, &DeviceList{&goodBD2N1, - &DeviceList{&goodBD3N1, &DeviceList{&goodBD4N1, &DeviceList{&goodBD5N1, &DeviceList{&goodBD6N1, nil}}}}}}, - resource.MustParse("1Gi"), 6}, []v1alpha1.BlockDevice{goodBD1N1, goodBD2N1, goodBD3N1, goodBD4N1, goodBD5N1, goodBD6N1}, false}, - {"six node LinkedList, two BD required of 1G", args{bdLinkedList(6, []int{1, 2, 3, 4, 5, 6}), resource.MustParse("1G"), 2}, nil, true}, - {"six node LinkedList, two BD required of 1G", args{bdLinkedList(6, []int{1, 2, 3, 4, 6, 6}), resource.MustParse("1G"), 2}, - []v1alpha1.BlockDevice{bdGen(5, 6), bdGen(6, 6)}, false}, - {"six node LinkedList, two BD required of 1G", args{bdLinkedList(6, []int{1, 2, 4, 4, 6, 6}), resource.MustParse("1G"), 2}, - []v1alpha1.BlockDevice{bdGen(3, 4), bdGen(4, 4)}, false}, - {"six node LinkedList, three BD required of 1G", args{bdLinkedList(6, []int{1, 4, 4, 4, 6, 6}), resource.MustParse("1G"), 3}, - []v1alpha1.BlockDevice{bdGen(2, 4), bdGen(3, 4), bdGen(4, 4)}, false}, - {"six node LinkedList, three BD required of 1G", args{bdLinkedList(6, []int{5, 10, 15, 20, 25, 30}), resource.MustParse("1G"), 3}, - nil, true}, - {"six node LinkedList, two BD required of 1G", args{bdLinkedList(6, []int{1, 1, 10, 20, 25, 30}), resource.MustParse("1G"), 2}, - []v1alpha1.BlockDevice{bdGen(1, 1), bdGen(2, 1)}, false}, - {"six node LinkedList, two BD required of 6G", args{bdLinkedList(6, []int{5, 10, 10, 20, 25, 30}), resource.MustParse("6G"), 2}, - []v1alpha1.BlockDevice{bdGen(2, 10), bdGen(3, 10)}, false}, - {"six node LinkedList with unsorted BD sizes, two BD required of 1G", args{bdLinkedList(6, []int{25, 30, 6, 10, 20, 6}), resource.MustParse("1G"), 2}, - nil, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - newHead, got, err := tt.args.head.Select(tt.args.size, tt.args.count) - if (err != nil) != tt.wantErr { - t.Fatalf("Select() error = %v, wantErr %v", err, tt.wantErr) - } - _ = newHead - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Select(...), got %v, want %v", len(got), len(tt.want)) - } - }) - } -} - -func bdGen(bdSuffix int, GBsize int) v1alpha1.BlockDevice { - parse := resource.MustParse(fmt.Sprintf("%d", GBsize) + "G") - return v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{ - Kind: "BlockDevice", - APIVersion: "openebs.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("bd-%d", bdSuffix), - Namespace: "openebs", - Labels: map[string]string{types.HostNameLabelKey: "node-X"}, - }, - Spec: v1alpha1.DeviceSpec{ - Capacity: v1alpha1.DeviceCapacity{Storage: uint64(parse.Value())}, - NodeAttributes: v1alpha1.NodeAttribute{NodeName: "node-X"}, - }, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}, - } -} - -func bdLinkedList(limit int, size []int) *DeviceList { - if len(size) != limit { - return nil - } - var head *DeviceList - for i := limit - 1; i >= 0; i-- { - tmp := New(bdGen(i+1, size[i])) - tmp.next = head - head = tmp - } - return head -} - -func BenchmarkSelect(b *testing.B) { - type args struct { - head *DeviceList - size resource.Quantity - count int - } - benchmarks := []struct { - name string - args args - want []v1alpha1.BlockDevice - }{ - {"six node LinkedList, two BD required of 6G", args{bdLinkedList(6, []int{5, 10, 10, 20, 25, 30}), resource.MustParse("6G"), 2}, - []v1alpha1.BlockDevice{bdGen(2, 10), bdGen(3, 10)}}, - {"six node LinkedList with unsorted BD sizes, two BD required of 1G", args{bdLinkedList(6, []int{25, 30, 6, 10, 20, 6}), resource.MustParse("1G"), 2}, - nil}, - } - for _, bm := range benchmarks { - b.Run(bm.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - _, _, _ = bm.args.head.Select(bm.args.size, bm.args.count) - } - }) - } -} diff --git a/pkg/generate/testdata_test.go b/pkg/generate/testdata_test.go deleted file mode 100644 index c70a1e95..00000000 --- a/pkg/generate/testdata_test.go +++ /dev/null @@ -1,440 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package generate - -import ( - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var cstorCSIpod = corev1.Pod{ - TypeMeta: metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "fake-cstor-CSI", Namespace: "openebs", - Labels: map[string]string{"openebs.io/version": "1.9.0", "openebs.io/component-name": "openebs-cstor-csi-controller"}}, - Status: corev1.PodStatus{Phase: corev1.PodRunning}, -} - -var node1 = corev1.Node{ - TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: "v1"}, ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Status: corev1.NodeStatus{Phase: corev1.NodeRunning}} - -var node2 = corev1.Node{ - TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: "v1"}, ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Status: corev1.NodeStatus{Phase: corev1.NodeRunning}} - -var node3 = corev1.Node{ - TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: "v1"}, ObjectMeta: metav1.ObjectMeta{Name: "node3", Labels: map[string]string{"kubernetes.io/hostname": "node3"}}, - Status: corev1.NodeStatus{Phase: corev1.NodeRunning}} - -var activeBDwEXT4 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "ext4", Mountpoint: "/dev/sda"}}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var inactiveBDwEXT4 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd1-inactive", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "ext4", Mountpoint: "/dev/sda"}, Capacity: v1alpha1.DeviceCapacity{Storage: 6711000}}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceInactive}} - -var activeUnclaimedUnforattedBD = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/dev/sda"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD1N1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd1-n1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd1n1"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD2N1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd2-n1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd2n1"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD3N1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd3-n1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd3n1"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD4N1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd4-n1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd4n1"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD5N1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd5-n1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd5n1"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD6N1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd6-n1", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node1"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd6n1"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} -var goodBD1N2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd1-n2", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/dev/sda"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD2N2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd2-n2", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/dev/sda"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD3N2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd3-n2", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd3n2"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD4N2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd4-n2", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd4n2"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD5N2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd5-n2", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd5n2"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD6N2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd6-n2", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node2"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/mnt/bd6n2"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD1N3 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd1-n3", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node3"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/dev/sdc"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var goodBD2N3 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "Blockdevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd2-n3", Namespace: "openebs", - Labels: map[string]string{"kubernetes.io/hostname": "node3"}}, - Spec: v1alpha1.DeviceSpec{FileSystem: v1alpha1.FileSystemInfo{Type: "", Mountpoint: "/dev/sdc"}, Capacity: v1alpha1.DeviceCapacity{Storage: 1074000000}, - Path: "/dev/sda"}, - Status: v1alpha1.DeviceStatus{ClaimState: v1alpha1.BlockDeviceUnclaimed, State: v1alpha1.BlockDeviceActive}} - -var mirrorCSPC = cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{GenerateName: "cstor", Namespace: "openebs"}, - Spec: cstorv1.CStorPoolClusterSpec{Pools: []cstorv1.PoolSpec{ - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - DataRaidGroups: []cstorv1.RaidGroup{{CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{ - BlockDeviceName: "bd1-n1"}, {BlockDeviceName: "bd2-n1"}}}}, PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolMirrored)}}, - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, - DataRaidGroups: []cstorv1.RaidGroup{{CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{ - BlockDeviceName: "bd1-n2"}, {BlockDeviceName: "bd2-n2"}}}}, PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolMirrored)}}, - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node3"}, - DataRaidGroups: []cstorv1.RaidGroup{{CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{ - BlockDeviceName: "bd1-n3"}, {BlockDeviceName: "bd2-n3"}}}}, PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolMirrored)}}}}} - -var mirrorCSPCFourBDs = cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{GenerateName: "cstor", Namespace: "openebs"}, - Spec: cstorv1.CStorPoolClusterSpec{Pools: []cstorv1.PoolSpec{ - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - DataRaidGroups: []cstorv1.RaidGroup{{CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n1"}, {BlockDeviceName: "bd2-n1"}}}, - {CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd3-n1"}, {BlockDeviceName: "bd4-n1"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolMirrored)}}, - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, - DataRaidGroups: []cstorv1.RaidGroup{{CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n2"}, {BlockDeviceName: "bd2-n2"}}}, - {CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd3-n2"}, {BlockDeviceName: "bd4-n2"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolMirrored)}}}}} - -var mirrorCSPCstr = `apiVersion: cstor.openebs.io/v1 -kind: CStorPoolCluster -metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs -spec: - pools: - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n1 - nodeSelector: - kubernetes.io/hostname: node1 - poolConfig: - dataRaidGroupType: mirror - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n2 - nodeSelector: - kubernetes.io/hostname: node2 - poolConfig: - dataRaidGroupType: mirror - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n3 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n3 - nodeSelector: - kubernetes.io/hostname: node3 - poolConfig: - dataRaidGroupType: mirror - -` -var raidzCSPCThreeBDTwoNode = cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{GenerateName: "cstor", Namespace: "openebs"}, - Spec: cstorv1.CStorPoolClusterSpec{Pools: []cstorv1.PoolSpec{ - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - DataRaidGroups: []cstorv1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n1"}, {BlockDeviceName: "bd2-n1"}, {BlockDeviceName: "bd3-n1"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolRaidz)}}, - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, - DataRaidGroups: []cstorv1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n2"}, {BlockDeviceName: "bd2-n2"}, {BlockDeviceName: "bd3-n2"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolRaidz)}}}}} - -var raidzCSPCstr = `apiVersion: cstor.openebs.io/v1 -kind: CStorPoolCluster -metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs -spec: - pools: - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd3-n1 - nodeSelector: - kubernetes.io/hostname: node1 - poolConfig: - dataRaidGroupType: raidz - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd3-n2 - nodeSelector: - kubernetes.io/hostname: node2 - poolConfig: - dataRaidGroupType: raidz - -` -var raidz2CSPCSixBDTwoNode = cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{GenerateName: "cstor", Namespace: "openebs"}, - Spec: cstorv1.CStorPoolClusterSpec{Pools: []cstorv1.PoolSpec{ - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - DataRaidGroups: []cstorv1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n1"}, - {BlockDeviceName: "bd2-n1"}, {BlockDeviceName: "bd3-n1"}, {BlockDeviceName: "bd4-n1"}, {BlockDeviceName: "bd5-n1"}, {BlockDeviceName: "bd6-n1"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolRaidz2)}}, - {NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, - DataRaidGroups: []cstorv1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n2"}, - {BlockDeviceName: "bd2-n2"}, {BlockDeviceName: "bd3-n2"}, {BlockDeviceName: "bd4-n2"}, {BlockDeviceName: "bd5-n2"}, {BlockDeviceName: "bd6-n2"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolRaidz2)}}}}} - -var raidz2CSPCstr = `apiVersion: cstor.openebs.io/v1 -kind: CStorPoolCluster -metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs -spec: - pools: - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd3-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd4-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd5-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd6-n1 - nodeSelector: - kubernetes.io/hostname: node1 - poolConfig: - dataRaidGroupType: raidz2 - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd3-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd4-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd5-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd6-n2 - nodeSelector: - kubernetes.io/hostname: node2 - poolConfig: - dataRaidGroupType: raidz2 - -` - -var cspc1 = `apiVersion: cstor.openebs.io/v1 -kind: CStorPoolCluster -metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs -spec: - pools: - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1 - nodeSelector: - kubernetes.io/hostname: node1 - poolConfig: - dataRaidGroupType: stripe - -` -var StripeThreeNodeTwoDev = `apiVersion: cstor.openebs.io/v1 -kind: CStorPoolCluster -metadata: - creationTimestamp: null - generateName: cstor - namespace: openebs -spec: - pools: - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n1 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n1 - nodeSelector: - kubernetes.io/hostname: node1 - poolConfig: - dataRaidGroupType: stripe - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n2 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n2 - nodeSelector: - kubernetes.io/hostname: node2 - poolConfig: - dataRaidGroupType: stripe - - dataRaidGroups: - - blockDevices: - # /dev/sda 1.0GiB - - blockDeviceName: bd1-n3 - # /dev/sda 1.0GiB - - blockDeviceName: bd2-n3 - nodeSelector: - kubernetes.io/hostname: node3 - poolConfig: - dataRaidGroupType: stripe - -` -var threeNodeTwoDevCSPC = cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{GenerateName: "cstor", Namespace: "openebs"}, - Spec: cstorv1.CStorPoolClusterSpec{Pools: []cstorv1.PoolSpec{{ - NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - DataRaidGroups: []cstorv1.RaidGroup{{ - CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n1"}, {BlockDeviceName: "bd2-n1"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolStriped)}}, - { - NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, - DataRaidGroups: []cstorv1.RaidGroup{{ - CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n2"}, {BlockDeviceName: "bd2-n2"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolStriped)}}, - { - NodeSelector: map[string]string{"kubernetes.io/hostname": "node3"}, - DataRaidGroups: []cstorv1.RaidGroup{{ - CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1-n3"}, {BlockDeviceName: "bd2-n3"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolStriped)}}}}, -} - -var cspc1Struct = cstorv1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolCluster", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{GenerateName: "cstor", Namespace: "openebs"}, - Spec: cstorv1.CStorPoolClusterSpec{Pools: []cstorv1.PoolSpec{{ - NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - DataRaidGroups: []cstorv1.RaidGroup{{ - CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd1"}}}}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: string(cstorv1.PoolStriped)}}}}, -} diff --git a/pkg/persistentvolumeclaim/cstor.go b/pkg/persistentvolumeclaim/cstor.go deleted file mode 100644 index 0e92b413..00000000 --- a/pkg/persistentvolumeclaim/cstor.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "fmt" - "time" - - cstortypes "github.com/openebs/api/v2/pkg/apis/types" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/printers" -) - -const ( - cstorPvcInfoTemplate = ` -{{.Name}} Details : ------------------- -NAME : {{.Name}} -NAMESPACE : {{.Namespace}} -CAS TYPE : {{.CasType}} -BOUND VOLUME : {{.BoundVolume}} -ATTACHED TO NODE : {{.AttachedToNode}} -POOL : {{.Pool}} -STORAGE CLASS : {{.StorageClassName}} -SIZE : {{.Size}} -USED : {{.Used}} -CV STATUS : {{.CVStatus}} -PV STATUS : {{.PVStatus}} -MOUNTED BY : {{.MountPods}} -` - - detailsFromCVC = ` -Additional Details from CVC : ------------------------------ -NAME : {{ .metadata.name }} -REPLICA COUNT : {{ .spec.provision.replicaCount }} -POOL INFO : {{ .status.poolInfo}} -VERSION : {{ .versionDetails.status.current}} -UPGRADING : {{if eq .versionDetails.status.current .versionDetails.desired}}false{{else}}true{{end}} -` -) - -// DescribeCstorVolumeClaim describes a cstor storage engine PersistentVolumeClaim -func DescribeCstorVolumeClaim(c *client.K8sClient, pvc *corev1.PersistentVolumeClaim, pv *corev1.PersistentVolume, mountPods string) error { - // Create Empty template objects and fill gradually when underlying sub CRs are identified. - pvcInfo := util.CstorPVCInfo{} - - pvcInfo.Name = pvc.Name - pvcInfo.Namespace = pvc.Namespace - pvcInfo.BoundVolume = pvc.Spec.VolumeName - pvcInfo.CasType = util.CstorCasType - pvcInfo.StorageClassName = *pvc.Spec.StorageClassName - pvcInfo.MountPods = mountPods - - if pv != nil { - pvcInfo.PVStatus = pv.Status.Phase - } - - // fetching the underlying CStorVolume for the PV, to get the phase and size and notify the user - // if the CStorVolume is not found. - cv, err := c.GetCV(pvc.Spec.VolumeName) - if err != nil { - fmt.Println("Underlying CstorVolume is not found for: ", pvc.Name) - } else { - pvcInfo.Size = util.ConvertToIBytes(cv.Spec.Capacity.String()) - pvcInfo.CVStatus = cv.Status.Phase - } - - // fetching the underlying CStorVolumeConfig for the PV, to get the cvc info and Pool Name and notify the user - // if the CStorVolumeConfig is not found. - cvc, err := c.GetCVC(pvc.Spec.VolumeName) - if err != nil { - fmt.Println("Underlying CstorVolumeConfig is not found for: ", pvc.Name) - } else { - pvcInfo.Pool = cvc.Labels[cstortypes.CStorPoolClusterLabelKey] - } - - // fetching the underlying CStorVolumeAttachment for the PV, to get the attached to node and notify the user - // if the CStorVolumeAttachment is not found. - cva, err := c.GetCVA(util.CVAVolnameKey + "=" + pvc.Spec.VolumeName) - if err != nil { - pvcInfo.AttachedToNode = util.NotAvailable - fmt.Println("Underlying CstorVolumeAttachment is not found for: ", pvc.Name) - } else { - pvcInfo.AttachedToNode = cva.Spec.Volume.OwnerNodeID - } - - // fetching the underlying CStorVolumeReplicas for the PV, to list their details and notify the user - // none of the replicas are running if the CStorVolumeReplicas are not found. - cvrs, err := c.GetCVRs(cstortypes.PersistentVolumeLabelKey + "=" + pvc.Spec.VolumeName) - if err == nil && len(cvrs.Items) > 0 { - pvcInfo.Used = util.ConvertToIBytes(util.GetUsedCapacityFromCVR(cvrs)) - } - - // Printing the Filled Details of the Cstor PVC - _ = util.PrintByTemplate("pvc", cstorPvcInfoTemplate, pvcInfo) - - // fetching the underlying TargetPod for the PV, to display its relevant details and notify the user - // if the TargetPod is not found. - tgtPod, err := c.GetCVTargetPod(pvc.Name, pvc.Spec.VolumeName) - if err == nil { - fmt.Printf("\nTarget Details :\n----------------\n") - var rows []metav1.TableRow - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - tgtPod.Namespace, tgtPod.Name, - util.GetReadyContainers(tgtPod.Status.ContainerStatuses), - tgtPod.Status.Phase, util.Duration(time.Since(tgtPod.ObjectMeta.CreationTimestamp.Time)), - tgtPod.Status.PodIP, tgtPod.Spec.NodeName}}) - util.TablePrinter(util.PodDetailsColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } else { - fmt.Printf("\nTarget Details :\n----------------\nNo target pod exists for the CstorVolume\n") - } - - // If CVRs are found list them and show relevant details else notify the user none of the replicas are - // running if not found - if cvrs != nil && len(cvrs.Items) > 0 { - fmt.Printf("\nReplica Details :\n-----------------\n") - var rows []metav1.TableRow - for _, cvr := range cvrs.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{cvr.Name, - util.ConvertToIBytes(cvr.Status.Capacity.Total), - util.ConvertToIBytes(cvr.Status.Capacity.Used), - cvr.Status.Phase, - util.Duration(time.Since(cvr.ObjectMeta.CreationTimestamp.Time))}}) - } - util.TablePrinter(util.CstorReplicaColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } else { - fmt.Printf("\nReplica Details :\n-----------------\nNo running replicas found\n") - } - - if cvc != nil { - util.TemplatePrinter(detailsFromCVC, cvc) - } - - return nil -} diff --git a/pkg/persistentvolumeclaim/cstor_test.go b/pkg/persistentvolumeclaim/cstor_test.go deleted file mode 100644 index f3d0d61f..00000000 --- a/pkg/persistentvolumeclaim/cstor_test.go +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "testing" - - openebsFakeClientset "github.com/openebs/api/v2/pkg/client/clientset/versioned/fake" - "github.com/openebs/openebsctl/pkg/client" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes/fake" -) - -func TestDescribeCstorVolumeClaim(t *testing.T) { - type args struct { - c *client.K8sClient - pvc *corev1.PersistentVolumeClaim - pv *corev1.PersistentVolume - mountPods string - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "All Valid Values", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor, &cstorTargetPod), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pv: &cstorPV1, - pvc: &cstorPVC1, - mountPods: "", - }, - wantErr: false, - }, - { - name: "PV missing", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pv: nil, - pvc: &cstorPVC1, - mountPods: "", - }, - wantErr: false, - }, - { - name: "CV missing", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv2, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pv: &cstorPV1, - pvc: &cstorPVC1, - mountPods: "", - }, - wantErr: false, - }, - { - name: "CVC missing", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pv: &cstorPV1, - pvc: &cstorPVC1, - mountPods: "", - }, - wantErr: false, - }, - { - name: "CVA missing", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pv: &cstorPV1, - pvc: &cstorPVC1, - mountPods: "", - }, - wantErr: false, - }, - { - name: "CVRs missing", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva2, &cvc1, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pv: &cstorPV1, - pvc: &cstorPVC1, - mountPods: "", - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DescribeCstorVolumeClaim(tt.args.c, tt.args.pvc, tt.args.pv, tt.args.mountPods); (err != nil) != tt.wantErr { - t.Errorf("DescribeCstorVolumeClaim() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/persistentvolumeclaim/debug.go b/pkg/persistentvolumeclaim/debug.go deleted file mode 100644 index d508ec74..00000000 --- a/pkg/persistentvolumeclaim/debug.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "fmt" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" -) - -// Debug manages various implementations of PersistentVolumeClaim Describing -func Debug(pvcs []string, namespace string, openebsNs string) error { - if len(pvcs) == 0 || pvcs == nil { - return errors.New("please provide atleast one pvc name to describe") - } - // Clienset creation - k := client.NewK8sClient(openebsNs) - - // 1. Get a list of required PersistentVolumeClaims - var pvcList *corev1.PersistentVolumeClaimList - pvcList, err := k.GetPVCs(namespace, pvcs, "") - if len(pvcList.Items) == 0 || err != nil { - return errors.New("no pvcs found corresponding to the names") - } - // 2. Get the namespaces - nsMap, _ := k.GetOpenEBSNamespaceMap() - // 3. Range over the list of PVCs - for _, pvc := range pvcList.Items { - // 4. Fetch the storage class, used to get the cas-type - sc, _ := k.GetSC(*pvc.Spec.StorageClassName) - pv, _ := k.GetPV(pvc.Spec.VolumeName) - // 5. Get cas type - casType := util.GetCasType(pv, sc) - // 6. Assign a namespace corresponding to the engine - if openebsNs == "" { - if val, ok := nsMap[casType]; ok { - k.Ns = val - } - } - // 7. Debug the volume based on its casType - if desc, ok := CasDebugMap()[casType]; ok { - err = desc(k, &pvc, pv) - if err != nil { - continue - } - } else { - fmt.Printf("Debugging is currently not supported for %s Cas Type PVCs\n", casType) - } - } - return nil -} - -// CasDebugMap returns a map cas-types to functions for persistentvolumeclaim debugging -func CasDebugMap() map[string]func(*client.K8sClient, *corev1.PersistentVolumeClaim, *corev1.PersistentVolume) error { - // a good hack to implement immutable maps in Golang & also write tests for it - return map[string]func(*client.K8sClient, *corev1.PersistentVolumeClaim, *corev1.PersistentVolume) error{ - util.CstorCasType: DebugCstorVolumeClaim, - } -} diff --git a/pkg/persistentvolumeclaim/debug_cstor.go b/pkg/persistentvolumeclaim/debug_cstor.go deleted file mode 100644 index 043a67ed..00000000 --- a/pkg/persistentvolumeclaim/debug_cstor.go +++ /dev/null @@ -1,363 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "errors" - "fmt" - - cstortypes "github.com/openebs/api/v2/pkg/apis/types" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/printers" -) - -// DebugCstorVolumeClaim is used to debug a cstor volume by calling various modules -func DebugCstorVolumeClaim(k *client.K8sClient, pvc *corev1.PersistentVolumeClaim, pv *corev1.PersistentVolume) error { - // 1. Main Struture Creation which contains all cstor CRs, this structure will be passed across all modules. - var cstorResources util.CstorVolumeResources - cstorResources.PVC = pvc - cstorResources.PV = pv - // 2. Fill in the available CRs - if pv != nil { - cv, _ := k.GetCV(pv.Name) - cstorResources.CV = cv - cvc, _ := k.GetCVC(pv.Name) - cstorResources.CVC = cvc - cva, _ := k.GetCVA(util.CVAVolnameKey + "=" + pv.Name) - cstorResources.CVA = cva - cvrs, _ := k.GetCVRs(cstortypes.PersistentVolumeLabelKey + "=" + pv.Name) - cstorResources.CVRs = cvrs - } - sc, _ := k.GetSC(*pvc.Spec.StorageClassName) - // 3. Fill in the Pool and Blockdevice Details - if sc != nil { - cspc, _ := k.GetCSPC(sc.Parameters["cstorPoolCluster"]) - cstorResources.CSPC = cspc - if cspc != nil { - cspis, _ := k.GetCSPIs(nil, "openebs.io/cas-type=cstor,openebs.io/cstor-pool-cluster="+cspc.Name) - cstorResources.CSPIs = cspis - expectedBlockDevicesInPool := make(map[string]bool) - // This map contains the list of BDs we specified at the time of Pool Creation - for _, pool := range cspc.Spec.Pools { - dataRaidGroups := pool.DataRaidGroups - for _, dataRaidGroup := range dataRaidGroups { - for _, bdName := range dataRaidGroup.GetBlockDevices() { - expectedBlockDevicesInPool[bdName] = false - } - } - } - // This list contains the list of BDs which are actually present in the system. - var presentBlockDevicesInPool []string - for _, pool := range cspis.Items { - raidGroupsInPool := pool.GetAllRaidGroups() - for _, item := range raidGroupsInPool { - presentBlockDevicesInPool = append(presentBlockDevicesInPool, item.GetBlockDevices()...) - } - } - - // Mark the present BDs are true. - cstorResources.PresentBDs, _ = k.GetBDs(presentBlockDevicesInPool, "") - for _, item := range cstorResources.PresentBDs.Items { - if _, ok := expectedBlockDevicesInPool[item.Name]; ok { - expectedBlockDevicesInPool[item.Name] = true - } - } - cstorResources.ExpectedBDs = expectedBlockDevicesInPool - cstorResources.BDCs, _ = k.GetBDCs(nil, "openebs.io/cstor-pool-cluster="+cspc.Name) - - } - } - // 4. Call the resource showing module - _ = resourceStatus(cstorResources) - _ = displayPVCEvents(*k, cstorResources) - _ = displayCVCEvents(*k, cstorResources) - _ = displayCVREvents(*k, cstorResources) - _ = displayCSPIEvents(*k, cstorResources) - _ = displayCSPCEvents(*k, cstorResources) - _ = displayBDCEvents(*k, cstorResources) - return nil -} - -func resourceStatus(crs util.CstorVolumeResources) error { - // 1. Fetch the total and usage details and humanize them - var totalCapacity, usedCapacity, availableCapacity string - totalCapacity = util.ConvertToIBytes(crs.PVC.Spec.Resources.Requests.Storage().String()) - if crs.CVRs != nil { - usedCapacity = util.ConvertToIBytes(util.GetUsedCapacityFromCVR(crs.CVRs)) - } - // 2. Calculate the available capacity and usage percentage is used capacity is available - if usedCapacity != "" { - availableCapacity = util.GetAvailableCapacity(totalCapacity, usedCapacity) - percentage := util.GetUsedPercentage(totalCapacity, usedCapacity) - if percentage >= 80.00 { - availableCapacity = util.ColorText(availableCapacity, util.Red) - } else { - availableCapacity = util.ColorText(availableCapacity, util.Green) - } - } - // 3. Display the usage status - fmt.Println("Volume Usage Stats:") - fmt.Println("-------------------") - - util.TablePrinter(util.VolumeTotalAndUsageDetailColumnDefinitions, []metav1.TableRow{{Cells: []interface{}{totalCapacity, usedCapacity, availableCapacity}}}, printers.PrintOptions{}) - - fmt.Println() - fmt.Println("Related CR Statuses:") - fmt.Println("--------------------") - - var crStatusRows []metav1.TableRow - if crs.PV != nil { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"PersistentVolume", crs.PV.Name, util.ColorStringOnStatus(string(crs.PV.Status.Phase))}}) - } else { - crStatusRows = append( - crStatusRows, - metav1.TableRow{Cells: []interface{}{"PersistentVolume", "", util.ColorStringOnStatus(util.NotFound)}}, - ) - } - if crs.CV != nil { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"CstorVolume", crs.CV.Name, util.ColorStringOnStatus(string(crs.CV.Status.Phase))}}) - } else { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"CstorVolume", "", util.ColorStringOnStatus(util.NotFound)}}) - } - if crs.CVC != nil { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"CstorVolumeConfig", crs.CVC.Name, util.ColorStringOnStatus(string(crs.CVC.Status.Phase))}}) - } else { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"CstorVolumeConfig", "", util.ColorStringOnStatus(util.NotFound)}}) - } - if crs.CVA != nil { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"CstorVolumeAttachment", crs.CVA.Name, util.ColorStringOnStatus(util.Attached)}}) - } else { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"CstorVolumeAttachment", "", util.ColorStringOnStatus(util.CVANotAttached)}}) - } - // 4. Display the CRs statuses - util.TablePrinter(util.CstorVolumeCRStatusColumnDefinitions, crStatusRows, printers.PrintOptions{}) - - fmt.Println() - fmt.Println("Replica Statuses:") - fmt.Println("-----------------") - crStatusRows = []metav1.TableRow{} - if crs.CVRs != nil { - for _, item := range crs.CVRs.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{item.Kind, item.Name, util.ColorStringOnStatus(string(item.Status.Phase))}}) - } - } - // 5. Display the CRs statuses - util.TablePrinter(util.CstorVolumeCRStatusColumnDefinitions, crStatusRows, printers.PrintOptions{}) - - fmt.Println() - fmt.Println("BlockDevice and BlockDeviceClaim Statuses:") - fmt.Println("------------------------------------------") - crStatusRows = []metav1.TableRow{} - if crs.PresentBDs != nil { - for _, item := range crs.PresentBDs.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{item.Kind, item.Name, util.ColorStringOnStatus(string(item.Status.State))}}) - } - for key, val := range crs.ExpectedBDs { - if !val { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"BlockDevice", key, util.ColorStringOnStatus(util.NotFound)}}) - } - } - } - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{"", "", ""}}) - if crs.BDCs != nil { - for _, item := range crs.BDCs.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{item.Kind, item.Name, util.ColorStringOnStatus(string(item.Status.Phase))}}) - } - } - // 6. Display the BDs and BDCs statuses - util.TablePrinter(util.CstorVolumeCRStatusColumnDefinitions, crStatusRows, printers.PrintOptions{}) - - fmt.Println() - fmt.Println("Pool Instance Statuses:") - fmt.Println("-----------------------") - crStatusRows = []metav1.TableRow{} - if crs.CSPIs != nil { - for _, item := range crs.CSPIs.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{item.Kind, item.Name, util.ColorStringOnStatus(string(item.Status.Phase))}}) - } - } - // 7. Display the Pool statuses - util.TablePrinter(util.CstorVolumeCRStatusColumnDefinitions, crStatusRows, printers.PrintOptions{}) - - return nil -} - -func displayPVCEvents(k client.K8sClient, crs util.CstorVolumeResources) error { - // 1. Set the namespace of the resource to the client - k.Ns = crs.PVC.Namespace - // 2. Fetch the events of the concerned PVC. - // The PVCs donot have the Kind filled, thus we have hardcoded here. - events, err := k.GetEvents(fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=PersistentVolumeClaim", crs.PVC.Name)) - // 3. Display the events - fmt.Println() - if err == nil && len(events.Items) != 0 { - fmt.Println("Checking PVC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCross, len(events.Items)), util.Red), "-------->") - var crStatusRows []metav1.TableRow - for _, event := range events.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{event.InvolvedObject.Name, event.Action, event.Reason, event.Message, util.ColorStringOnStatus(event.Type)}}) - } - util.TablePrinter(util.EventsColumnDefinitions, crStatusRows, printers.PrintOptions{}) - return nil - } else if err == nil && len(events.Items) == 0 { - fmt.Println("Checking PVC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCheck, len(events.Items)), util.Green), "-------->") - return nil - } else { - return err - } -} - -func displayBDCEvents(k client.K8sClient, crs util.CstorVolumeResources) error { - if crs.BDCs != nil && len(crs.BDCs.Items) != 0 { - // 1. Set the namespace of the resource to the client - k.Ns = crs.BDCs.Items[0].Namespace - // 2. Fetch the events of the concerned BDC - fmt.Println() - var crStatusRows []metav1.TableRow - for _, BDC := range crs.BDCs.Items { - events, err := k.GetEvents(fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=BlockDeviceClaim", BDC.Name)) - // 3. Display the events - if err == nil && len(events.Items) != 0 { - for _, event := range events.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{event.InvolvedObject.Name, event.Action, event.Reason, event.Message, util.ColorStringOnStatus(event.Type)}}) - } - } - } - if len(crStatusRows) == 0 { - fmt.Println("Checking BDC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCheck, len(crStatusRows)), util.Green), "-------->") - return nil - } else { - fmt.Println("Checking BDC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCross, len(crStatusRows)), util.Red), "-------->") - util.TablePrinter(util.EventsColumnDefinitions, crStatusRows, printers.PrintOptions{}) - return nil - } - } - return errors.New("no BDC present to display events") -} - -func displayCVCEvents(k client.K8sClient, crs util.CstorVolumeResources) error { - if crs.CVC != nil { - // 1. Set the namespace of the resource to the client - k.Ns = crs.CVC.Namespace - // 2. Fetch the events of the concerned CVC - events, err := k.GetEvents(fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=CStorVolumeConfig", crs.CVC.Name)) - // 3. Display the events - fmt.Println() - if err == nil && len(events.Items) != 0 { - fmt.Println("Checking CVC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCross, len(events.Items)), util.Red), "-------->") - var crStatusRows []metav1.TableRow - for _, event := range events.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{event.InvolvedObject.Name, event.Action, event.Reason, event.Message, util.ColorStringOnStatus(event.Type)}}) - } - defer util.TablePrinter(util.EventsColumnDefinitions, crStatusRows, printers.PrintOptions{}) - return nil - } else if err == nil && len(events.Items) == 0 { - fmt.Println("Checking CVC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCheck, len(events.Items)), util.Green), "-------->") - return nil - } else { - return err - } - } else { - return errors.New("no CVC present to display events") - } -} - -func displayCSPCEvents(k client.K8sClient, crs util.CstorVolumeResources) error { - if crs.CSPC != nil { - // 1. Set the namespace of the resource to the client - k.Ns = crs.CSPC.Namespace - // 2. Fetch the events of the concerned PVC. - // The PVCs donot have the Kind filled, thus we have hardcoded here. - events, err := k.GetEvents(fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=CStorPoolCluster", crs.CSPC.Name)) - // 3. Display the events - fmt.Println() - if err == nil && len(events.Items) != 0 { - fmt.Println("Checking CSPC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCross, len(events.Items)), util.Red), "-------->") - var crStatusRows []metav1.TableRow - for _, event := range events.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{event.InvolvedObject.Name, event.Action, event.Reason, event.Message, util.ColorStringOnStatus(event.Type)}}) - } - util.TablePrinter(util.EventsColumnDefinitions, crStatusRows, printers.PrintOptions{}) - return nil - } else if err == nil && len(events.Items) == 0 { - fmt.Println("Checking CSPC Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCheck, len(events.Items)), util.Green), "-------->") - return nil - } else { - return err - } - } else { - return errors.New("no CSPC present to display events") - } -} - -func displayCSPIEvents(k client.K8sClient, crs util.CstorVolumeResources) error { - if crs.CSPIs != nil && len(crs.CSPIs.Items) != 0 { - // 1. Set the namespace of the resource to the client - k.Ns = crs.CSPIs.Items[0].Namespace - // 2. Fetch the events of the concerned CSPIs - fmt.Println() - var crStatusRows []metav1.TableRow - for _, CSPI := range crs.CSPIs.Items { - events, err := k.GetEvents(fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=CStorPoolInstance", CSPI.Name)) - // 3. Display the events - if err == nil && len(events.Items) != 0 { - for _, event := range events.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{event.InvolvedObject.Name, event.Action, event.Reason, event.Message, util.ColorStringOnStatus(event.Type)}}) - } - } - } - if len(crStatusRows) == 0 { - fmt.Println("Checking CSPI Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCheck, len(crStatusRows)), util.Green), "-------->") - return nil - } else { - fmt.Println("Checking CSPI Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCross, len(crStatusRows)), util.Red), "-------->") - util.TablePrinter(util.EventsColumnDefinitions, crStatusRows, printers.PrintOptions{}) - return nil - } - } - return errors.New("no CSPIs present to display events") -} - -func displayCVREvents(k client.K8sClient, crs util.CstorVolumeResources) error { - if crs.CVRs != nil && len(crs.CVRs.Items) != 0 { - // 1. Set the namespace of the resource to the client - k.Ns = crs.CVRs.Items[0].Namespace - // 2. Fetch the events of the concerned CVRs - fmt.Println() - var crStatusRows []metav1.TableRow - for _, CVR := range crs.CVRs.Items { - events, err := k.GetEvents(fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=CStorVolumeReplica", CVR.Name)) - // 3. Display the events - if err == nil && len(events.Items) != 0 { - for _, event := range events.Items { - crStatusRows = append(crStatusRows, metav1.TableRow{Cells: []interface{}{event.InvolvedObject.Name, event.Action, event.Reason, event.Message, util.ColorStringOnStatus(event.Type)}}) - } - } - } - if len(crStatusRows) == 0 { - fmt.Println("Checking CVR Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCheck, len(crStatusRows)), util.Green), "-------->") - return nil - } else { - fmt.Println("Checking CVR Events:", util.ColorText(fmt.Sprintf(" %s %d! ", util.UnicodeCross, len(crStatusRows)), util.Red), "-------->") - util.TablePrinter(util.EventsColumnDefinitions, crStatusRows, printers.PrintOptions{}) - return nil - } - } - return errors.New("no CVRs present to display events") -} diff --git a/pkg/persistentvolumeclaim/debug_cstor_test.go b/pkg/persistentvolumeclaim/debug_cstor_test.go deleted file mode 100644 index c3cc776a..00000000 --- a/pkg/persistentvolumeclaim/debug_cstor_test.go +++ /dev/null @@ -1,964 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "fmt" - "testing" - "time" - - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - cstortypes "github.com/openebs/api/v2/pkg/apis/types" - openebsFakeClientset "github.com/openebs/api/v2/pkg/client/clientset/versioned/fake" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" - fake2 "k8s.io/client-go/kubernetes/typed/core/v1/fake" - k8stest "k8s.io/client-go/testing" -) - -func TestDebugCstorVolumeClaim(t *testing.T) { - type args struct { - k *client.K8sClient - pvc *corev1.PersistentVolumeClaim - pv *corev1.PersistentVolume - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with all valid values", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cv1, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with PV missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: nil, - }, - false, - }, - { - "Test with CV missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with CVC Missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cv1, &cva1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with CVA missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cv1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with CVRs missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cv1, &cva1, &cvc1), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with cspc missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspi1, &cspi2, &bd1, &bd2, &bdc1, &bdc2, &cv1, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with cspis missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &bd1, &bd2, &bdc1, &bdc2, &cv1, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with bds missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bdc1, &bdc2, &cv1, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - { - "Test with bdcs missing", - args{ - k: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPVC1, &cstorSc), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cspc, &cspi1, &cspi2, &bd1, &bd2, &cv1, &cva1, &cvc1, &cvr1, &cvr2), - }, - pvc: &cstorPVC1, - pv: &cstorPV1, - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DebugCstorVolumeClaim(tt.args.k, tt.args.pvc, tt.args.pv); (err != nil) != tt.wantErr { - t.Errorf("DebugCstorVolumeClaim() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_displayBDCEvents(t *testing.T) { - type args struct { - k client.K8sClient - crs util.CstorVolumeResources - bdcFunc func(*client.K8sClient, map[string]corev1.EventList) - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid values and events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&bdcEvent1, &bdcEvent2), - }, - crs: util.CstorVolumeResources{ - BDCs: &bdcList, - }, - bdcFunc: eventFunc, - }, - false, - }, - { - "Test with valid values with no events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - BDCs: &bdcList, - }, - bdcFunc: nil, - }, - false, - }, - { - "Test with valid values with no BDCs", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - BDCs: nil, - }, - bdcFunc: nil, - }, - true, - }, - { - "Test with valid values with no BDCList as empty", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - BDCs: &v1alpha1.BlockDeviceClaimList{ - Items: []v1alpha1.BlockDeviceClaim{}, - }, - }, - bdcFunc: nil, - }, - true, - }, - { - "Test with valid values with no events errored out", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - BDCs: &v1alpha1.BlockDeviceClaimList{ - Items: []v1alpha1.BlockDeviceClaim{}, - }, - }, - bdcFunc: noEventFunc, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.bdcFunc != nil { - tt.args.bdcFunc(&tt.args.k, map[string]corev1.EventList{ - "bdc-1": {Items: []corev1.Event{bdcEvent1}}, - "bdc-2": {Items: []corev1.Event{bdcEvent2}}, - }) - } - if err := displayBDCEvents(tt.args.k, tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("displayBDCEvents() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_displayCSPCEvents(t *testing.T) { - type args struct { - k client.K8sClient - crs util.CstorVolumeResources - cspcFunc func(*client.K8sClient, map[string]corev1.EventList) - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid values and events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cspcEvent), - }, - crs: util.CstorVolumeResources{ - CSPC: &cspc, - }, - cspcFunc: nil, - }, - false, - }, - { - "Test with valid values with no events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPC: &cspc, - }, - cspcFunc: nil, - }, - false, - }, - { - "Test with valid values with no CSPC", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPC: nil, - }, - cspcFunc: nil, - }, - true, - }, - { - "Test with valid values with no events errored out", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPC: &cspc, - }, - cspcFunc: noEventFunc, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.cspcFunc != nil { - tt.args.cspcFunc(&tt.args.k, map[string]corev1.EventList{}) - } - if err := displayCSPCEvents(tt.args.k, tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("displayCSPCEvents() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_displayCSPIEvents(t *testing.T) { - type args struct { - k client.K8sClient - crs util.CstorVolumeResources - cspiFunc func(*client.K8sClient, map[string]corev1.EventList) - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid values and events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cspiEvent1, &cspiEvent2), - }, - crs: util.CstorVolumeResources{ - CSPIs: &cspiList, - }, - cspiFunc: eventFunc, - }, - false, - }, - { - "Test with valid values with no events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPIs: &cspiList, - }, - cspiFunc: nil, - }, - false, - }, - { - "Test with valid values with no CSPIs", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPIs: nil, - }, - cspiFunc: nil, - }, - true, - }, - { - "Test with valid values with no CSPIList as empty", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPIs: &v1.CStorPoolInstanceList{ - Items: []v1.CStorPoolInstance{}, - }, - }, - cspiFunc: nil, - }, - true, - }, - { - "Test with valid values with no events errored out", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CSPIs: &v1.CStorPoolInstanceList{ - Items: []v1.CStorPoolInstance{}, - }, - }, - cspiFunc: noEventFunc, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.cspiFunc != nil { - tt.args.cspiFunc(&tt.args.k, map[string]corev1.EventList{ - "cspc-1": {Items: []corev1.Event{cspiEvent1}}, - "cspc-2": {Items: []corev1.Event{cspiEvent2}}, - }) - } - if err := displayCSPIEvents(tt.args.k, tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("displayCSPIEvents() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_displayCVCEvents(t *testing.T) { - type args struct { - k client.K8sClient - crs util.CstorVolumeResources - cvcFunc func(*client.K8sClient, map[string]corev1.EventList) - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid values and events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cvcEvent1, &cvcEvent2), - }, - crs: util.CstorVolumeResources{ - CVC: &cvc1, - }, - cvcFunc: nil, - }, - false, - }, - { - "Test with valid values with no events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVC: &cvc1, - }, - cvcFunc: nil, - }, - false, - }, - { - "Test with valid values with no CVC", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVC: nil, - }, - cvcFunc: nil, - }, - true, - }, - { - "Test with valid values with no events errored out", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVC: &cvc1, - }, - cvcFunc: noEventFunc, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.cvcFunc != nil { - tt.args.cvcFunc(&tt.args.k, map[string]corev1.EventList{}) - } - if err := displayCVCEvents(tt.args.k, tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("displayCVCEvents() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_displayCVREvents(t *testing.T) { - type args struct { - k client.K8sClient - crs util.CstorVolumeResources - cvrfunc func(*client.K8sClient, map[string]corev1.EventList) - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid values and events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cvrEvent1, &cvrEvent2), - }, - crs: util.CstorVolumeResources{ - CVRs: &cvrList, - }, - cvrfunc: eventFunc, - }, - false, - }, - { - "Test with valid values with no events", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVRs: &cvrList, - }, - cvrfunc: nil, - }, - false, - }, - { - "Test with valid values with no CVRs", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVRs: nil, - }, - cvrfunc: nil, - }, - true, - }, - { - "Test with valid values with no CVRIList as empty", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVRs: &v1.CStorVolumeReplicaList{ - Items: []v1.CStorVolumeReplica{}, - }, - }, - cvrfunc: nil, - }, - true, - }, - { - "Test with valid values with no events errored out", - args{ - k: client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - CVRs: &v1.CStorVolumeReplicaList{ - Items: []v1.CStorVolumeReplica{}, - }, - }, - cvrfunc: noEventFunc, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.cvrfunc != nil { - tt.args.cvrfunc(&tt.args.k, map[string]corev1.EventList{ - "pvc-1-rep-1": {Items: []corev1.Event{cvrEvent1}}, - "pvc-1-rep-2": {Items: []corev1.Event{cvrEvent2}}, - }) - } - if err := displayCVREvents(tt.args.k, tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("displayCVREvents() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_displayPVCEvents(t *testing.T) { - type args struct { - k client.K8sClient - crs util.CstorVolumeResources - pvcFunc func(*client.K8sClient, map[string]corev1.EventList) - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with valid values and events", - args{ - k: client.K8sClient{ - Ns: "default", - K8sCS: fake.NewSimpleClientset(&pvcEvent1, &pvcEvent2), - }, - crs: util.CstorVolumeResources{ - PVC: &cstorPVC1, - }, - pvcFunc: nil, - }, - false, - }, - { - "Test with valid values with no events", - args{ - k: client.K8sClient{ - Ns: "default", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - PVC: &cstorPVC1, - }, - pvcFunc: nil, - }, - false, - }, - { - "Test with valid values with no events errored out", - args{ - k: client.K8sClient{ - Ns: "default", - K8sCS: fake.NewSimpleClientset(), - }, - crs: util.CstorVolumeResources{ - PVC: &cstorPVC1, - }, - pvcFunc: noEventFunc, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.pvcFunc != nil { - tt.args.pvcFunc(&tt.args.k, map[string]corev1.EventList{}) - } - if err := displayPVCEvents(tt.args.k, tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("displayPVCEvents() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_resourceStatus(t *testing.T) { - var cvrListWithMoreUsedCapacity = v1.CStorVolumeReplicaList{Items: []v1.CStorVolumeReplica{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "3.923GiB", - }, - Phase: v1.CVRStatusOnline, - }, - }}} - type args struct { - crs util.CstorVolumeResources - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - "Test with all valid values", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all PV absent", - args{crs: util.CstorVolumeResources{ - PV: nil, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all CV absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: nil, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all CVC absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: nil, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all CVA absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: nil, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all CVRs absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: nil, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all BDs absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: nil, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all BDCs absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: nil, - CSPIs: &cspiList, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all CSPIs absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: nil, - CSPC: &cspc, - }}, - false, - }, - { - "Test with all CSPC absent", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrList, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: nil, - }}, - false, - }, - { - "Test with all Used Capacity exceeding 80%", - args{crs: util.CstorVolumeResources{ - PV: &cstorPV1, - PVC: &cstorPVC1, - CV: &cv1, - CVC: &cvc1, - CVA: &cva1, - CVRs: &cvrListWithMoreUsedCapacity, - PresentBDs: &bdList, - ExpectedBDs: expectedBDs, - BDCs: &bdcList, - CSPIs: &cspiList, - CSPC: nil, - }}, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := resourceStatus(tt.args.crs); (err != nil) != tt.wantErr { - t.Errorf("resourceStatus() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func eventFunc(c *client.K8sClient, eventMap map[string]corev1.EventList) { - c.K8sCS.CoreV1().Events(c.Ns).(*fake2.FakeEvents).Fake.PrependReactor("*", "events", func(action k8stest.Action) (handled bool, ret runtime.Object, err error) { - listOpts, ok := action.(k8stest.ListActionImpl) - if ok { - val, matched := listOpts.ListRestrictions.Fields.RequiresExactMatch("involvedObject.name") - if matched { - if events, present := eventMap[val]; present { - return true, &events, nil - } else { - return true, nil, fmt.Errorf("invalid fieldSelector") - } - } else { - return true, nil, fmt.Errorf("invalid fieldSelector") - } - } else { - return true, nil, fmt.Errorf("invalid fieldSelector") - } - }) -} - -func noEventFunc(c *client.K8sClient, eventMap map[string]corev1.EventList) { - c.K8sCS.CoreV1().Events(c.Ns).(*fake2.FakeEvents).Fake.PrependReactor("*", "events", func(action k8stest.Action) (handled bool, ret runtime.Object, err error) { - return true, nil, fmt.Errorf("failed to list events") - }) -} diff --git a/pkg/persistentvolumeclaim/generic_test.go b/pkg/persistentvolumeclaim/generic_test.go index 87f77d23..4f1ceaf0 100644 --- a/pkg/persistentvolumeclaim/generic_test.go +++ b/pkg/persistentvolumeclaim/generic_test.go @@ -37,8 +37,8 @@ func TestDescribeGenericVolumeClaim(t *testing.T) { { name: "All Valid Values", args: args{ - pv: &cstorPV1, - pvc: &cstorPVC1, + pv: &zfsPV1, + pvc: &zfsPVC1, casType: "some-cas", mountPods: "", }, @@ -48,7 +48,7 @@ func TestDescribeGenericVolumeClaim(t *testing.T) { name: "PV missing", args: args{ pv: nil, - pvc: &cstorPVC1, + pvc: &zfsPVC1, casType: "some-cas", mountPods: "", }, diff --git a/pkg/persistentvolumeclaim/jiva.go b/pkg/persistentvolumeclaim/jiva.go deleted file mode 100644 index 2fd82e87..00000000 --- a/pkg/persistentvolumeclaim/jiva.go +++ /dev/null @@ -1,154 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "fmt" - "os" - "strings" - "time" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - "github.com/openebs/openebsctl/pkg/volume" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/printers" -) - -const ( - jivaPvcInfoTemplate = ` -{{.Name}} Details : -------------------- -NAME : {{.Name}} -NAMESPACE : {{.Namespace}} -CAS TYPE : {{.CasType}} -BOUND VOLUME : {{.BoundVolume}} -ATTACHED TO NODE : {{.AttachedToNode}} -JIVA VOLUME POLICY : {{.JVP}} -STORAGE CLASS : {{.StorageClassName}} -SIZE : {{.Size}} -JV STATUS : {{.JVStatus}} -PV STATUS : {{.PVStatus}} -MOUNTED BY : {{.MountPods}} -` -) - -// DescribeJivaVolumeClaim describes a jiva storage engine PersistentVolumeClaim -func DescribeJivaVolumeClaim(c *client.K8sClient, pvc *corev1.PersistentVolumeClaim, vol *corev1.PersistentVolume, mountPods string) error { - // 1. Get the JivaVolume Corresponding to the pvc name - jv, err := c.GetJV(pvc.Spec.VolumeName) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "failed to get JivaVolume for %s", pvc.Spec.VolumeName) - fmt.Println() - } - // 2. Fill in Jiva Volume Claim related details - jivaPvcInfo := util.JivaPVCInfo{ - Name: pvc.Name, - Namespace: pvc.Namespace, - CasType: util.JivaCasType, - BoundVolume: pvc.Spec.VolumeName, - StorageClassName: *pvc.Spec.StorageClassName, - Size: pvc.Spec.Resources.Requests.Storage().String(), - MountPods: mountPods, - } - if jv != nil { - jivaPvcInfo.AttachedToNode = jv.Labels["nodeID"] - jivaPvcInfo.JVP = jv.Annotations["openebs.io/volume-policy"] - jivaPvcInfo.JVStatus = jv.Status.Status - } - if vol != nil { - jivaPvcInfo.PVStatus = vol.Status.Phase - // 3. Print the Jiva Volume Claim information - _ = util.PrintByTemplate("jivaPvcInfo", jivaPvcInfoTemplate, jivaPvcInfo) - } else { - _ = util.PrintByTemplate("jivaPvcInfo", jivaPvcInfoTemplate, jivaPvcInfo) - _, _ = fmt.Fprintf(os.Stderr, "PersistentVolume %s, doesnot exist", pvc.Spec.VolumeName) - fmt.Println() - return nil - } - // 4. Print the Portal Information - replicaPodIPAndModeMap := make(map[string]string) - jvStatus := "" - if jv != nil { - util.TemplatePrinter(volume.JivaPortalTemplate, jv) - // Create Replica IP to Mode Map - if jv.Status.ReplicaStatuses != nil && len(jv.Status.ReplicaStatuses) != 0 { - for _, replicaStatus := range jv.Status.ReplicaStatuses { - replicaPodIPAndModeMap[strings.Split(replicaStatus.Address, ":")[1][2:]] = replicaStatus.Mode - } - } - jvStatus = jv.Status.Status - } else { - fmt.Println() - } - // 5. Fetch the Jiva controller and replica pod details - podList, err := c.GetJVTargetPod(vol.Name) - if err == nil { - fmt.Println("Controller and Replica Pod Details :") - fmt.Println("-----------------------------------") - var rows []metav1.TableRow - for _, pod := range podList.Items { - if !strings.Contains(pod.Name, "-ctrl-") { - mode := "" - // If the IP doesnot exist, keep the mode as empty - if val, ok := replicaPodIPAndModeMap[pod.Status.PodIP]; ok { - mode = val - } - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - pod.Namespace, pod.Name, mode, - pod.Spec.NodeName, pod.Status.Phase, pod.Status.PodIP, - util.GetReadyContainers(pod.Status.ContainerStatuses), - util.Duration(time.Since(pod.ObjectMeta.CreationTimestamp.Time))}}) - } else { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - pod.Namespace, pod.Name, jvStatus, - pod.Spec.NodeName, pod.Status.Phase, pod.Status.PodIP, - util.GetReadyContainers(pod.Status.ContainerStatuses), - util.Duration(time.Since(pod.ObjectMeta.CreationTimestamp.Time))}}) - } - } - util.TablePrinter(util.JivaPodDetailsColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } else { - fmt.Printf("Controller and Replica Pod Details :") - fmt.Println("-----------------------------------") - fmt.Println("No Controller and Replica pod exists for the JivaVolume") - } - // 6. Fetch the replica PVCs and create rows for cli-runtime - var rows []metav1.TableRow - pvcList, err := c.GetPVCs(c.Ns, nil, "openebs.io/component=jiva-replica,openebs.io/persistent-volume="+vol.Name) - if err != nil || len(pvcList.Items) == 0 { - fmt.Printf("No replicas found for the JivaVolume %s", vol.Name) - return nil - } - for _, pvc := range pvcList.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - pvc.Name, - pvc.Status.Phase, - pvc.Spec.VolumeName, - util.ConvertToIBytes(pvc.Spec.Resources.Requests.Storage().String()), - *pvc.Spec.StorageClassName, - util.Duration(time.Since(pvc.ObjectMeta.CreationTimestamp.Time)), - pvc.Spec.VolumeMode}}) - } - // 6. Print the replica details if present - fmt.Println() - fmt.Println("Replica Data Volume Details :") - fmt.Println("-----------------------------") - util.TablePrinter(util.JivaReplicaPVCColumnDefinations, rows, printers.PrintOptions{Wide: true}) - return nil -} diff --git a/pkg/persistentvolumeclaim/jiva_test.go b/pkg/persistentvolumeclaim/jiva_test.go deleted file mode 100644 index 03739d08..00000000 --- a/pkg/persistentvolumeclaim/jiva_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package persistentvolumeclaim - -import ( - "testing" - - "github.com/openebs/openebsctl/pkg/client" - corev1 "k8s.io/api/core/v1" -) - -func TestDescribeJivaVolumeClaim(t *testing.T) { - type args struct { - c *client.K8sClient - pvc *corev1.PersistentVolumeClaim - vol *corev1.PersistentVolume - mountPods string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DescribeJivaVolumeClaim(tt.args.c, tt.args.pvc, tt.args.vol, tt.args.mountPods); (err != nil) != tt.wantErr { - t.Errorf("DescribeJivaVolumeClaim() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/persistentvolumeclaim/persistentvolumeclaim.go b/pkg/persistentvolumeclaim/persistentvolumeclaim.go index fea3ec8d..12242658 100644 --- a/pkg/persistentvolumeclaim/persistentvolumeclaim.go +++ b/pkg/persistentvolumeclaim/persistentvolumeclaim.go @@ -84,14 +84,12 @@ func Describe(pvcs []string, namespace string, openebsNs string) error { func CasDescribeMap() map[string]func(*client.K8sClient, *corev1.PersistentVolumeClaim, *corev1.PersistentVolume, string) error { // a good hack to implement immutable maps in Golang & also write tests for it return map[string]func(*client.K8sClient, *corev1.PersistentVolumeClaim, *corev1.PersistentVolume, string) error{ - util.JivaCasType: DescribeJivaVolumeClaim, - util.CstorCasType: DescribeCstorVolumeClaim, - util.LVMCasType: DescribeLVMVolumeClaim, - util.ZFSCasType: DescribeZFSVolumeClaim, + util.LVMCasType: DescribeLVMVolumeClaim, + util.ZFSCasType: DescribeZFSVolumeClaim, } } -//GetMountPods filters the array of Pods and returns an array of Pods that mount the PersistentVolumeClaim +// GetMountPods filters the array of Pods and returns an array of Pods that mount the PersistentVolumeClaim func GetMountPods(pvcName string, nsPods []corev1.Pod) []corev1.Pod { var pods []corev1.Pod for _, pod := range nsPods { @@ -107,7 +105,7 @@ func GetMountPods(pvcName string, nsPods []corev1.Pod) []corev1.Pod { return pods } -//SortPods sorts the array of Pods by name +// SortPods sorts the array of Pods by name func SortPods(pods []corev1.Pod) []corev1.Pod { sort.Slice(pods, func(i, j int) bool { cmpKey := func(pod corev1.Pod) string { @@ -118,10 +116,10 @@ func SortPods(pods []corev1.Pod) []corev1.Pod { return pods } -//PodsToString Flattens the array of Pods and returns a string fit to display in the output +// PodsToString Flattens the array of Pods and returns a string fit to display in the output func PodsToString(pods []corev1.Pod) string { if len(pods) == 0 { - return "" + return "none" } str := "" for _, pod := range pods { diff --git a/pkg/persistentvolumeclaim/testdata_test.go b/pkg/persistentvolumeclaim/testdata_test.go index f20fdb94..aeea9679 100644 --- a/pkg/persistentvolumeclaim/testdata_test.go +++ b/pkg/persistentvolumeclaim/testdata_test.go @@ -19,14 +19,10 @@ package persistentvolumeclaim import ( "time" - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - cstortypes "github.com/openebs/api/v2/pkg/apis/types" lvm "github.com/openebs/lvm-localpv/pkg/apis/openebs.io/lvm/v1alpha1" "github.com/openebs/openebsctl/pkg/util" zfs "github.com/openebs/zfs-localpv/pkg/apis/openebs.io/zfs/v1" corev1 "k8s.io/api/core/v1" - v12 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -36,743 +32,6 @@ var ( blockFS = corev1.PersistentVolumeBlock ) -/**************** -* CSTOR -****************/ - -var nsCstor = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - }, - Spec: corev1.NamespaceSpec{Finalizers: []corev1.FinalizerName{corev1.FinalizerKubernetes}}, -} - -var cv1 = v1.CStorVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeSpec{ - Capacity: fourGigiByte, - TargetIP: "10.2.2.2", - TargetPort: "3002", - Iqn: "pvc1-some-fake-iqn", - TargetPortal: "10.2.2.2:3002", - ReplicationFactor: 3, - ConsistencyFactor: 0, - DesiredReplicationFactor: 0, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-1-rep-1", "some-id-2": "pvc-1-rep-2", "some-id-3": "pvc-1-rep-3"}, - }, - }, - Status: v1.CStorVolumeStatus{ - Phase: util.Healthy, - ReplicaStatuses: []v1.ReplicaStatus{{ID: "some-id-1", Mode: "Healthy"}, {ID: "some-id-2", Mode: "Healthy"}, {ID: "some-id-3", Mode: "Healthy"}}, - Capacity: fourGigiByte, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-1-rep-1", "some-id-2": "pvc-1-rep-2", "some-id-3": "pvc-1-rep-3"}, - }, - }, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{ - DependentsUpgraded: true, - Current: "2.11.0", - LastUpdateTime: metav1.Time{}, - }, - }, -} - -var cv2 = v1.CStorVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeSpec{ - Capacity: fourGigiByte, - TargetIP: "10.2.2.2", - TargetPort: "3002", - Iqn: "pvc1-some-fake-iqn", - TargetPortal: "10.2.2.2:3002", - ReplicationFactor: 3, - ConsistencyFactor: 0, - DesiredReplicationFactor: 0, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-2-rep-1"}, - }, - }, - Status: v1.CStorVolumeStatus{ - Phase: util.Healthy, - ReplicaStatuses: []v1.ReplicaStatus{{ID: "some-id-1", Mode: "Healthy"}}, - Capacity: fourGigiByte, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-2-rep-1"}, - }, - }, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{ - DependentsUpgraded: true, - Current: "2.11.0", - LastUpdateTime: metav1.Time{}, - }, - }, -} - -var cvc1 = v1.CStorVolumeConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeConfigSpec{Provision: v1.VolumeProvision{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - ReplicaCount: 3, - }}, - Publish: v1.CStorVolumeConfigPublish{}, - Status: v1.CStorVolumeConfigStatus{PoolInfo: []string{"pool-1", "pool-2", "pool-3"}}, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{Current: "2.11.0"}, - }, -} - -var cvc2 = v1.CStorVolumeConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeConfigSpec{Provision: v1.VolumeProvision{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - ReplicaCount: 3, - }}, - Publish: v1.CStorVolumeConfigPublish{}, - Status: v1.CStorVolumeConfigStatus{PoolInfo: []string{"pool-1"}}, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{Current: "2.11.0"}, - }, -} - -var cva1 = v1.CStorVolumeAttachment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-cva", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"Volname": "pvc-1", "nodeID": "node-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeAttachmentSpec{Volume: v1.VolumeInfo{OwnerNodeID: "node-1"}}, -} - -var cva2 = v1.CStorVolumeAttachment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2-cva", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"Volname": "pvc-2", "nodeID": "node-2"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeAttachmentSpec{Volume: v1.VolumeInfo{OwnerNodeID: "node-2"}}, -} - -var cvr1 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr2 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr3 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-3", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr4 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2-rep-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvrList = v1.CStorVolumeReplicaList{Items: []v1.CStorVolumeReplica{cvr1, cvr2}} - -var cstorSc = v12.StorageClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-sc", - CreationTimestamp: metav1.Time{Time: time.Now()}, - }, - Provisioner: "cstor.csi.openebs.io", - Parameters: map[string]string{"cstorPoolCluster": "cspc"}, -} - -var ( - cstorScName = "cstor-sc" - cstorVolumeMode = corev1.PersistentVolumeFilesystem - cstorPVC1 = corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - Namespace: "default", - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - Resources: corev1.ResourceRequirements{Requests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceStorage: fourGigiByte}}, - VolumeName: "pvc-1", - StorageClassName: &cstorScName, - VolumeMode: &cstorVolumeMode, - }, - Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimBound, Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}}, - } -) - -var ( - cstorPVC2 = corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - Namespace: "default", - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - Resources: corev1.ResourceRequirements{Requests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceStorage: fourGigiByte}}, - VolumeName: "pvc-2", - StorageClassName: &cstorScName, - VolumeMode: &cstorVolumeMode, - }, - Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimBound, Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}}, - } -) - -var ( - cstorPV1 = corev1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: corev1.PersistentVolumeSpec{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - ClaimRef: &corev1.ObjectReference{ - Namespace: "default", - Name: "cstor-pvc-1", - }, - PersistentVolumeReclaimPolicy: "Retain", - StorageClassName: cstorScName, - VolumeMode: &cstorVolumeMode, - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{ - Driver: "cstor.csi.openebs.io", - }}, - }, - Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeBound}, - } -) - -var ( - cstorPV2 = corev1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - }, - Spec: corev1.PersistentVolumeSpec{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - ClaimRef: &corev1.ObjectReference{ - Namespace: "default", - Name: "cstor-pvc-2", - }, - PersistentVolumeReclaimPolicy: "Retain", - StorageClassName: cstorScName, - VolumeMode: &cstorVolumeMode, - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{ - Driver: "cstor.csi.openebs.io", - }}, - }, - Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeBound}, - } -) - -var cbkp = v1.CStorBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bkp-name", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: v1.CStorBackupSpec{ - BackupName: "bkp-name", - VolumeName: "pvc-1", - SnapName: "snap-name", - PrevSnapName: "prev-snap-name", - BackupDest: "10.2.2.7", - LocalSnap: true, - }, - Status: v1.BKPCStorStatusDone, -} - -var ccbkp = v1.CStorCompletedBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "completed-bkp-name", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: v1.CStorCompletedBackupSpec{ - BackupName: "completed-bkp-name", - VolumeName: "pvc-1", - SecondLastSnapName: "secondlast-snapshot-name", - LastSnapName: "last-snapshot-name", - }, -} - -var crestore = v1.CStorRestore{ - ObjectMeta: metav1.ObjectMeta{ - Name: "restore-name", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: v1.CStorRestoreSpec{ - RestoreName: "restore-name", - VolumeName: "pvc-1", - RestoreSrc: "10.2.2.7", - MaxRetryCount: 3, - RetryCount: 2, - StorageClass: "cstor-sc", - Size: fourGigiByte, - Local: true, - }, -} - -var cstorTargetPod = corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "restore-name", - Namespace: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"openebs.io/persistent-volume-claim": "cstor-pvc-1", "openebs.io/persistent-volume": "pvc-1", "openebs.io/target": "cstor-target"}, - Finalizers: []string{}, - }, - Spec: corev1.PodSpec{NodeName: "node-1"}, - Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{Ready: true}, {Ready: true}, {Ready: true}}, PodIP: "10.2.2.2", Phase: "Running"}, -} - -var cspc = v1.CStorPoolCluster{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "cspc", - Namespace: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - }, - Spec: v1.CStorPoolClusterSpec{ - Pools: []v1.PoolSpec{{ - DataRaidGroups: []v1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []v1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd-1"}}}, - {CStorPoolInstanceBlockDevices: []v1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd-2"}}}, - {CStorPoolInstanceBlockDevices: []v1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd-3"}}}, - }, - }}, - }, -} - -var cspi1 = v1.CStorPoolInstance{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspc-1", - Namespace: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{ - "openebs.io/cstor-pool-cluster": "cspc", - "openebs.io/cas-type": "cstor", - }, - }, - Spec: v1.CStorPoolInstanceSpec{ - DataRaidGroups: []v1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []v1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd-1"}}}, - }, - }, - Status: v1.CStorPoolInstanceStatus{Phase: "ONLINE"}, -} - -var cspi2 = v1.CStorPoolInstance{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspc-2", - Namespace: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{ - "openebs.io/cstor-pool-cluster": "cspc", - "openebs.io/cas-type": "cstor", - }, - }, - Spec: v1.CStorPoolInstanceSpec{ - DataRaidGroups: []v1.RaidGroup{ - {CStorPoolInstanceBlockDevices: []v1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd-2"}}}, - }, - }, - Status: v1.CStorPoolInstanceStatus{Phase: "ONLINE"}, -} - -var cspiList = v1.CStorPoolInstanceList{Items: []v1.CStorPoolInstance{cspi1, cspi2}} - -/**************** -* BDC & BDCs - ****************/ - -var bd1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "bd-1", Namespace: "cstor"}, - Spec: v1alpha1.DeviceSpec{ - Path: "/dev/sdb", - Capacity: v1alpha1.DeviceCapacity{Storage: uint64(132131321)}, - FileSystem: v1alpha1.FileSystemInfo{ - Type: "zfs_member", - Mountpoint: "/var/some-fake-point", - }, - NodeAttributes: v1alpha1.NodeAttribute{ - NodeName: "fake-node-1", - }, - }, - Status: v1alpha1.DeviceStatus{ - ClaimState: "Claimed", - State: "Active", - }, -} -var bd2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "bd-2", Namespace: "cstor"}, - Spec: v1alpha1.DeviceSpec{ - Path: "/dev/sdb", - Capacity: v1alpha1.DeviceCapacity{Storage: uint64(132131321)}, - FileSystem: v1alpha1.FileSystemInfo{ - Type: "zfs_member", - Mountpoint: "/var/some-fake-point", - }, - NodeAttributes: v1alpha1.NodeAttribute{ - NodeName: "fake-node-1", - }, - }, - Status: v1alpha1.DeviceStatus{ - ClaimState: "Claimed", - State: "Active", - }, -} - -var bdList = v1alpha1.BlockDeviceList{ - Items: []v1alpha1.BlockDevice{bd1, bd2}, -} - -var bdc1 = v1alpha1.BlockDeviceClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bdc-1", - Namespace: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - }, - Status: v1alpha1.DeviceClaimStatus{Phase: "Bound"}, -} - -var bdc2 = v1alpha1.BlockDeviceClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bdc-2", - Namespace: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - }, - Status: v1alpha1.DeviceClaimStatus{Phase: "Bound"}, -} - -var bdcList = v1alpha1.BlockDeviceClaimList{Items: []v1alpha1.BlockDeviceClaim{bdc1, bdc2}} - -var expectedBDs = map[string]bool{ - "bdc-1": true, - "bdc-2": true, - "bdc-3": false, -} - -/**************** -* EVENTS -****************/ - -var pvcEvent1 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-pvc-1.time1", - Namespace: "default", - UID: "some-random-event-uuid-1", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "PersistentVolumeClaim", - Namespace: "default", - Name: "cstor-pvc-1", - UID: "some-random-pvc-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var pvcEvent2 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-pvc-1.time2", - Namespace: "default", - UID: "some-random-event-uuid-2", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "PersistentVolumeClaim", - Namespace: "default", - Name: "cstor-pvc-1", - UID: "some-random-pvc-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cvcEvent1 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-3", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorVolumeConfig", - Namespace: "cstor", - Name: "pvc-1", - UID: "some-random-cvc-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cvcEvent2 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1.time2", - Namespace: "cstor", - UID: "some-random-event-uuid-4", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorVolumeConfig", - Namespace: "cstor", - Name: "pvc-1", - UID: "some-random-cvc-uuid-2", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var bdcEvent1 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bdc-1.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-5", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "BlockDeviceClaim", - Namespace: "cstor", - Name: "bdc-1", - UID: "some-random-bdc-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var bdcEvent2 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bdc-2.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-6", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "BlockDeviceClaim", - Namespace: "cstor", - Name: "bdc-2", - UID: "some-random-bdc-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cspiEvent1 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspc-1.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-7", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorPoolInstance", - Namespace: "cstor", - Name: "cspc-1", - UID: "some-random-cspi-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cspiEvent2 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspc-2.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-8", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorPoolInstance", - Namespace: "cstor", - Name: "cspc-2", - UID: "some-random-cspi-uuid-2", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cspcEvent = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cspc.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-9", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorPoolCluster", - Namespace: "cstor", - Name: "cspc", - UID: "some-random-cspc-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cvrEvent1 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-1.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-10", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorVolumeReplica", - Namespace: "cstor", - Name: "pvc-1-rep-1", - UID: "some-random-cvr-uuid-1", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - -var cvrEvent2 = corev1.Event{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-2.time1", - Namespace: "cstor", - UID: "some-random-event-uuid-11", - }, - InvolvedObject: corev1.ObjectReference{ - Kind: "CStorVolumeReplica", - Namespace: "cstor", - Name: "pvc-1-rep-2", - UID: "some-random-cvr-uuid-2", - }, - Reason: "some-fake-reason", - Message: "some-fake-message", - Count: 1, - Type: "Warning", - Action: "some-fake-action", -} - /**************** * LVM LOCAL PV ****************/ diff --git a/pkg/storage/cstor.go b/pkg/storage/cstor.go deleted file mode 100644 index 78062937..00000000 --- a/pkg/storage/cstor.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package storage - -import ( - "fmt" - "time" - - "github.com/docker/go-units" - - "github.com/openebs/api/v2/pkg/apis/types" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/printers" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - "github.com/pkg/errors" -) - -const cStorPoolInstanceInfoTemplate = ` -{{.Name}} Details : ----------------- -NAME : {{.Name}} -HOSTNAME : {{.HostName}} -SIZE : {{.Size}} -FREE CAPACITY : {{.FreeCapacity}} -READ ONLY STATUS : {{.ReadOnlyStatus}} -STATUS : {{.Status}} -RAID TYPE : {{.RaidType}} -` - -// GetCstorPools lists the pools -func GetCstorPools(c *client.K8sClient, pools []string) ([]metav1.TableColumnDefinition, []metav1.TableRow, error) { - cpools, err := c.GetCSPIs(pools, "") - if err != nil { - return nil, nil, errors.Wrap(err, "error listing pools") - } - var rows []metav1.TableRow - for _, item := range cpools.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - item.ObjectMeta.Name, - item.ObjectMeta.Labels["kubernetes.io/hostname"], - util.ConvertToIBytes(item.Status.Capacity.Free.String()), - util.ConvertToIBytes(item.Status.Capacity.Total.String()), - item.Status.ReadOnly, - item.Status.ProvisionedReplicas, - item.Status.HealthyReplicas, - string(item.Status.Phase), - util.Duration(time.Since(item.ObjectMeta.CreationTimestamp.Time))}}) - } - if len(cpools.Items) == 0 { - return nil, nil, fmt.Errorf("no cstor pools are found") - } - return util.CstorPoolListColumnDefinations, rows, nil -} - -// DescribeCstorPool method runs info command and make call to DisplayPoolInfo to display the results -func DescribeCstorPool(c *client.K8sClient, poolName string) error { - pools, err := c.GetCSPIs([]string{poolName}, "") - if err != nil { - return errors.Wrap(err, "error getting pool info") - } - if len(pools.Items) == 0 { - return fmt.Errorf("cstor-pool %s not found", poolName) - } - poolInfo := pools.Items[0] - poolDetails := util.PoolInfo{ - Name: poolInfo.Name, - HostName: poolInfo.Spec.HostName, - Size: util.ConvertToIBytes(poolInfo.Status.Capacity.Total.String()), - FreeCapacity: util.ConvertToIBytes(poolInfo.Status.Capacity.Free.String()), - ReadOnlyStatus: poolInfo.Status.ReadOnly, - Status: poolInfo.Status.Phase, - RaidType: poolInfo.Spec.PoolConfig.DataRaidGroupType, - } - // Fetch all the raid groups in the CSPI - RaidGroupsInPool := poolInfo.GetAllRaidGroups() - - // Fetch all the block devices in the raid groups associated to the CSPI - var BlockDevicesInPool []string - for _, item := range RaidGroupsInPool { - BlockDevicesInPool = append(BlockDevicesInPool, item.GetBlockDevices()...) - } - - // Printing the filled details of the Pool - err = util.PrintByTemplate("pool", cStorPoolInstanceInfoTemplate, poolDetails) - if err != nil { - return err - } - - // Fetch info for every block device - var bdRows []metav1.TableRow - for _, item := range BlockDevicesInPool { - bd, err := c.GetBD(item) - if err != nil { - fmt.Printf("Could not find the blockdevice : %s\n", item) - } else { - bdRows = append(bdRows, metav1.TableRow{Cells: []interface{}{bd.Name, units.BytesSize(float64(bd.Spec.Capacity.Storage)), bd.Status.State}}) - } - } - if len(bdRows) != 0 { - fmt.Printf("\nBlockdevice details :\n" + "---------------------\n") - util.TablePrinter(util.BDListColumnDefinations, bdRows, printers.PrintOptions{Wide: true}) - } else { - fmt.Printf("Could not find any blockdevice that belongs to the pool\n") - } - - // Fetch info for provisional replica - var cvrRows []metav1.TableRow - CVRsInPool, err := c.GetCVRs(types.CStorPoolInstanceNameLabelKey + "=" + poolName) - if err != nil { - fmt.Printf("None of the replicas are running") - } else { - for _, cvr := range CVRsInPool.Items { - pvcName := "" - pv, err := c.GetPV(cvr.Labels["openebs.io/persistent-volume"]) - if err == nil { - pvcName = pv.Spec.ClaimRef.Name - } - cvrRows = append(cvrRows, metav1.TableRow{Cells: []interface{}{ - cvr.Name, - pvcName, - util.ConvertToIBytes(cvr.Status.Capacity.Total), - cvr.Status.Phase}}) - } - } - if len(cvrRows) != 0 { - fmt.Printf("\nReplica Details :\n-----------------\n") - util.TablePrinter(util.PoolReplicaColumnDefinations, cvrRows, printers.PrintOptions{Wide: true}) - } - return nil -} diff --git a/pkg/storage/cstor_test.go b/pkg/storage/cstor_test.go deleted file mode 100644 index 1fdb2b8e..00000000 --- a/pkg/storage/cstor_test.go +++ /dev/null @@ -1,169 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package storage - -import ( - "fmt" - "reflect" - "testing" - - fakecstor "github.com/openebs/api/v2/pkg/client/clientset/versioned/fake" - "github.com/openebs/api/v2/pkg/client/clientset/versioned/typed/cstor/v1/fake" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - corefake "k8s.io/client-go/kubernetes/fake" - k8stest "k8s.io/client-go/testing" -) - -func TestGetCstorPool(t *testing.T) { - type args struct { - c *client.K8sClient - poolName []string - } - tests := []struct { - name string - args args - cstorfunc func(sClient *client.K8sClient) - want []metav1.TableRow - wantErr bool - }{ - { - "no cstor pool found", - args{c: &client.K8sClient{Ns: "openebs", OpenebsCS: fakecstor.NewSimpleClientset()}, - poolName: nil}, - cspiNotFound, - nil, - true, - }, - { - "two cstor pool found", - args{c: &client.K8sClient{Ns: "openebs", OpenebsCS: fakecstor.NewSimpleClientset(&cspi1, &cspi2)}, - poolName: nil, - }, - nil, - []metav1.TableRow{ - {Cells: []interface{}{"pool-1", "node1", "174.0GiB", "188.1GiB", false, int32(2), int32(2), "ONLINE"}}, - {Cells: []interface{}{"pool-2", "node2", "174.0GiB", "188.1GiB", false, int32(2), int32(2), "ONLINE"}}}, - - false, - }, - { - "no pool-3 cstor pool found", - args{c: &client.K8sClient{Ns: "openebs", OpenebsCS: fakecstor.NewSimpleClientset(&cspi1, &cspi2)}, - poolName: []string{"pool-3"}, - }, - cspiNotFound, - nil, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.cstorfunc != nil { - tt.cstorfunc(tt.args.c) - } - if head, row, err := GetCstorPools(tt.args.c, tt.args.poolName); (err != nil) != tt.wantErr { - t.Errorf("GetCstorPool() error = %v, wantErr %v", err, tt.wantErr) - } else if err == nil { - if len(row) != len(tt.want) { - t.Errorf("GetCstorPool() returned %d rows, wanted %d elements", len(row), len(tt.want)) - } - for i, cspi := range row { - if !reflect.DeepEqual(cspi.Cells[0:8], tt.want[i].Cells) { - t.Errorf("GetCstorPool() returned %v want = %v", row, tt.want) - } - } - if !reflect.DeepEqual(head, util.CstorPoolListColumnDefinations) { - t.Errorf("GetCstorPools() returned wrong headers = %v want = %v", head, - util.CstorPoolListColumnDefinations) - } - } - // TODO: Check all but the last item of want - }) - } -} - -func TestDescribeCstorPool(t *testing.T) { - type args struct { - c *client.K8sClient - poolName string - } - tests := []struct { - name string - args args - cstorfunc func(sClient *client.K8sClient) - wantErr bool - }{ - {"no cstor pool exist", - args{c: &client.K8sClient{Ns: "cstor", OpenebsCS: fakecstor.NewSimpleClientset()}, - poolName: ""}, - // a GET on resource which don't exist, returns an error automatically - nil, - true, - }, - {"cspi-3 does not exist", - args{c: &client.K8sClient{Ns: "cstor", OpenebsCS: fakecstor.NewSimpleClientset()}, - poolName: "cspi-3"}, - nil, - true, - }, - {"cspi-1 exists but Namespace mismatched", - args{c: &client.K8sClient{Ns: "fake", OpenebsCS: fakecstor.NewSimpleClientset(&cspi1)}, - poolName: "cspi-1"}, - nil, - true, - }, - { - "cspi-1 exists and namespace matches but no BD", - args{c: &client.K8sClient{Ns: "openebs", OpenebsCS: fakecstor.NewSimpleClientset(&cspi1)}, - poolName: "pool-1"}, - nil, - false, - }, - { - "cspi-1 exists and BD exists", - args{c: &client.K8sClient{Ns: "openebs", OpenebsCS: fakecstor.NewSimpleClientset(&cspi1, &bd1)}, - poolName: "pool-1"}, - nil, - false, - }, - { - "cspi-1 exists, BD & CVR exists", - args{c: &client.K8sClient{Ns: "openebs", OpenebsCS: fakecstor.NewSimpleClientset(&cspi1, &bd1, &bd2, - &cvr1, &cvr2), K8sCS: corefake.NewSimpleClientset(&pv1)}, - poolName: "pool-1"}, - nil, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DescribeCstorPool(tt.args.c, tt.args.poolName); (err != nil) != tt.wantErr { - t.Errorf("DescribeCstorPool() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func cspiNotFound(c *client.K8sClient) { - // NOTE: Set the VERB & Resource correctly & make it work for single resources - c.OpenebsCS.CstorV1().(*fake.FakeCstorV1).Fake.PrependReactor("*", "*", func(action k8stest.Action) (handled bool, ret runtime.Object, err error) { - return true, nil, fmt.Errorf("failed to list CSPI") - }) -} diff --git a/pkg/storage/lvmlocalpv.go b/pkg/storage/lvmlocalpv.go index 7166c713..10437f1f 100644 --- a/pkg/storage/lvmlocalpv.go +++ b/pkg/storage/lvmlocalpv.go @@ -55,7 +55,7 @@ func GetVolumeGroups(c *client.K8sClient, vgs []string) ([]metav1.TableColumnDef } // 3. Actually print the table or return an error if len(rows) == 0 { - return nil, nil, util.HandleEmptyTableError("lvm Volumegroups", c.Ns, "") + return nil, nil, util.HandleEmptyTableError("lvm volumegroups", c.Ns, "") } return util.LVMvolgroupListColumnDefinitions, rows, nil } diff --git a/pkg/storage/lvmlocalpv_test.go b/pkg/storage/lvmlocalpv_test.go index 89d6eafa..60397642 100644 --- a/pkg/storage/lvmlocalpv_test.go +++ b/pkg/storage/lvmlocalpv_test.go @@ -46,10 +46,9 @@ func TestGetVolumeGroup(t *testing.T) { "no LVM volumegroups present", args{ c: &client.K8sClient{ - Ns: "lvmlocalpv", - K8sCS: nil, - OpenebsCS: nil, - LVMCS: fakelvmclient.NewSimpleClientset()}, + Ns: "lvmlocalpv", + K8sCS: nil, + LVMCS: fakelvmclient.NewSimpleClientset()}, vg: nil, lvmfunc: lvnNodeNotFound, }, @@ -121,7 +120,7 @@ func TestDescribeLVMvg(t *testing.T) { }{ { "no LVM vgs exist", - args{c: &client.K8sClient{Ns: "", LVMCS: fakelvmclient.NewSimpleClientset()}, lvmFunc: lvnNodeNotFound, vg: "cstor-pv1"}, + args{c: &client.K8sClient{Ns: "", LVMCS: fakelvmclient.NewSimpleClientset()}, lvmFunc: lvnNodeNotFound, vg: "some-vg-name"}, true, }, { diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index c6db0157..4c569b65 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -69,13 +69,13 @@ func Get(pools []string, openebsNS string, casType string) error { // CasList has a list of method implementations for different cas-types func CasList() []func(*client.K8sClient, []string) ([]metav1.TableColumnDefinition, []metav1.TableRow, error) { return []func(*client.K8sClient, []string) ([]metav1.TableColumnDefinition, []metav1.TableRow, error){ - GetCstorPools, GetVolumeGroups, GetZFSPools} + GetVolumeGroups, GetZFSPools} } // Describe manages various implementations of Storage Describing func Describe(storages []string, openebsNs, casType string) error { if len(storages) == 0 || storages == nil { - return errors.New("please provide atleast one pv name to describe") + return errors.New("please provide atleast one storage node name to describe") } // 1. Create the clientset k := client.NewK8sClient(openebsNs) @@ -121,9 +121,8 @@ func Describe(storages []string, openebsNs, casType string) error { func CasListMap() map[string]func(*client.K8sClient, []string) ([]metav1.TableColumnDefinition, []metav1.TableRow, error) { // a good hack to implement immutable maps in Golang & also write tests for it return map[string]func(*client.K8sClient, []string) ([]metav1.TableColumnDefinition, []metav1.TableRow, error){ - util.CstorCasType: GetCstorPools, - util.LVMCasType: GetVolumeGroups, - util.ZFSCasType: GetZFSPools, + util.LVMCasType: GetVolumeGroups, + util.ZFSCasType: GetZFSPools, } } @@ -131,13 +130,12 @@ func CasListMap() map[string]func(*client.K8sClient, []string) ([]metav1.TableCo func CasDescribeMap() map[string]func(*client.K8sClient, string) error { // a good hack to implement immutable maps in Golang & also write tests for it return map[string]func(*client.K8sClient, string) error{ - util.CstorCasType: DescribeCstorPool, - util.ZFSCasType: DescribeZFSNode, - util.LVMCasType: DescribeLVMvg, + util.ZFSCasType: DescribeZFSNode, + util.LVMCasType: DescribeLVMvg, } } -// CasDescribeList returns a list of functions which describe a Storage i.e. a pool/volume-group +// CasDescribeList returns a list of functions which describe a Storage i.e. a zfspool/volume-group func CasDescribeList() []func(*client.K8sClient, string) error { - return []func(*client.K8sClient, string) error{DescribeCstorPool, DescribeZFSNode, DescribeLVMvg} + return []func(*client.K8sClient, string) error{DescribeZFSNode, DescribeLVMvg} } diff --git a/pkg/storage/testdata_test.go b/pkg/storage/testdata_test.go index 4d442c67..e05e80a2 100644 --- a/pkg/storage/testdata_test.go +++ b/pkg/storage/testdata_test.go @@ -17,171 +17,12 @@ limitations under the License. package storage import ( - "time" - - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" - cstortypes "github.com/openebs/api/v2/pkg/apis/types" lvm "github.com/openebs/lvm-localpv/pkg/apis/openebs.io/lvm/v1alpha1" zfs "github.com/openebs/zfs-localpv/pkg/apis/openebs.io/zfs/v1" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var cspi1 = cstorv1.CStorPoolInstance{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolInstance", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "pool-1", Namespace: "openebs", - Finalizers: []string{"cstorpoolcluster.openebs.io/finalizer", "openebs.io/pool-protection"}, - Labels: map[string]string{ - "kubernetes.io/hostname": "node1", - "openebs.io/cas-type": "cstor", - "openebs.io/cstor-pool-cluster": "cassandra-pool", - "openebs.io/version": "2.11"}, - // OwnerReference links to the CSPC - }, - Spec: cstorv1.CStorPoolInstanceSpec{ - HostName: "node1", - NodeSelector: map[string]string{"kubernetes.io/hostname": "node1"}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: "stripe", WriteCacheGroupType: "", Compression: "off"}, - DataRaidGroups: []cstorv1.RaidGroup{{ - CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd-1", Capacity: 1234567, DevLink: "/dev/disk/by-id/abcd/def"}}}}, - WriteCacheRaidGroups: nil, - }, - Status: cstorv1.CStorPoolInstanceStatus{ - Conditions: []cstorv1.CStorPoolInstanceCondition{{ - Type: cstorv1.CSPIPoolLost, - Status: "True", - LastUpdateTime: metav1.Time{Time: time.Now()}, - LastTransitionTime: metav1.Time{Time: time.Now()}, - Reason: "PoolLost", - Message: "failed to importcstor-xyzabcd", - }}, - Phase: cstorv1.CStorPoolStatusOnline, - Capacity: cstorv1.CStorPoolInstanceCapacity{ - Used: resource.MustParse("18600Mi"), - Free: resource.MustParse("174Gi"), - Total: resource.MustParse("192600Mi"), - ZFS: cstorv1.ZFSCapacityAttributes{}, - }, - ReadOnly: false, ProvisionedReplicas: 2, HealthyReplicas: 2, - }, - VersionDetails: cstorv1.VersionDetails{Desired: "2.11", - Status: cstorv1.VersionStatus{Current: "2.11", State: cstorv1.ReconcileComplete, LastUpdateTime: metav1.Time{Time: time.Now()}}, - }, -} - -var bd1 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{Kind: "BlockDevice", APIVersion: "openebs.io/v1alpha1"}, - ObjectMeta: metav1.ObjectMeta{Name: "bd-1", Namespace: "openebs", - Annotations: map[string]string{ - "internal.openebs.io/partition-uuid": "49473bca-97c3-f340-beaf-dae9b2ce99bc", - "internal.openebs.io/uuid-scheme": "legacy"}}, - Spec: v1alpha1.DeviceSpec{Capacity: v1alpha1.DeviceCapacity{ - Storage: 123456789, - PhysicalSectorSize: 123456789, - LogicalSectorSize: 123456789, - }}, - Status: v1alpha1.DeviceStatus{}, -} - -var bd2 = v1alpha1.BlockDevice{ - TypeMeta: metav1.TypeMeta{ - Kind: "BlockDevice", - APIVersion: "openebs.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{Name: "bd-2", Namespace: "openebs"}, - Spec: v1alpha1.DeviceSpec{Capacity: v1alpha1.DeviceCapacity{ - Storage: 123456789, - PhysicalSectorSize: 123456789, - LogicalSectorSize: 123456789, - }, - FileSystem: v1alpha1.FileSystemInfo{Type: "zfs_member", Mountpoint: "/home/kubernetes/volume-abcd"}}, - Status: v1alpha1.DeviceStatus{ - ClaimState: "Claimed", - State: "Active", - }, -} - -var cvr1 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-1", - Labels: map[string]string{cstortypes.CStorPoolInstanceNameLabelKey: "pool-1", "openebs.io/persistent-volume": "pv1"}, - Namespace: "openebs", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr2 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-2", - Labels: map[string]string{cstortypes.CStorPoolInstanceNameLabelKey: "pool-1", "openebs.io/persistent-volume": "pv1"}, - Namespace: "openebs", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "40Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} -var pv1 = corev1.PersistentVolume{ - TypeMeta: metav1.TypeMeta{Kind: "PersistentVolume", APIVersion: "core/v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "pv1"}, - Spec: corev1.PersistentVolumeSpec{ClaimRef: &corev1.ObjectReference{Name: "mongopv1"}}, - Status: corev1.PersistentVolumeStatus{}, -} - -var cspi2 = cstorv1.CStorPoolInstance{ - TypeMeta: metav1.TypeMeta{Kind: "CStorPoolInstance", APIVersion: "cstor.openebs.io/v1"}, - ObjectMeta: metav1.ObjectMeta{Name: "pool-2", Namespace: "openebs", - Finalizers: []string{"cstorpoolcluster.openebs.io/finalizer", "openebs.io/pool-protection"}, - Labels: map[string]string{ - "kubernetes.io/hostname": "node2", - "openebs.io/cas-type": "cstor", - "openebs.io/cstor-pool-cluster": "cassandra-pool", - "openebs.io/version": "2.11"}, - // OwnerReference links to the CSPC - }, - Spec: cstorv1.CStorPoolInstanceSpec{ - HostName: "node2", - NodeSelector: map[string]string{"kubernetes.io/hostname": "node2"}, - PoolConfig: cstorv1.PoolConfig{DataRaidGroupType: "stripe", WriteCacheGroupType: "", Compression: "off"}, - DataRaidGroups: []cstorv1.RaidGroup{{ - CStorPoolInstanceBlockDevices: []cstorv1.CStorPoolInstanceBlockDevice{{BlockDeviceName: "bd2", Capacity: 1234567, DevLink: "/dev/disk/by-id/abcd/def"}}}}, - WriteCacheRaidGroups: nil, - }, - Status: cstorv1.CStorPoolInstanceStatus{ - Conditions: []cstorv1.CStorPoolInstanceCondition{{ - Type: cstorv1.CSPIPoolLost, - Status: "True", - LastUpdateTime: metav1.Time{Time: time.Now()}, - LastTransitionTime: metav1.Time{Time: time.Now()}, - Reason: "PoolLost", - Message: "failed to importcstor-xyzabcd", - }}, - Phase: cstorv1.CStorPoolStatusOnline, - Capacity: cstorv1.CStorPoolInstanceCapacity{ - Used: resource.MustParse("18600Mi"), - Free: resource.MustParse("174Gi"), - Total: resource.MustParse("192600Mi"), - ZFS: cstorv1.ZFSCapacityAttributes{}, - }, - ReadOnly: false, ProvisionedReplicas: 2, HealthyReplicas: 2, - }, - VersionDetails: cstorv1.VersionDetails{Desired: "2.11", - Status: cstorv1.VersionStatus{Current: "2.11", State: cstorv1.ReconcileComplete, LastUpdateTime: metav1.Time{Time: time.Now()}}, - }, -} - var ( fourGigiByte = resource.MustParse("4Gi") fiveGigiByte = resource.MustParse("5Gi") diff --git a/pkg/storage/zfslocalpv.go b/pkg/storage/zfslocalpv.go index 4166a31f..a4bf9046 100644 --- a/pkg/storage/zfslocalpv.go +++ b/pkg/storage/zfslocalpv.go @@ -40,6 +40,7 @@ func GetZFSPools(c *client.K8sClient, zfsnodes []string) ([]metav1.TableColumnDe if err != nil { return nil, nil, err } + var rows []metav1.TableRow for _, zfsNode := range zfsNodes.Items { rows = append(rows, metav1.TableRow{Cells: []interface{}{zfsNode.Name, ""}}) diff --git a/pkg/storage/zfslocalpv_test.go b/pkg/storage/zfslocalpv_test.go index 2421f6da..33397f9f 100644 --- a/pkg/storage/zfslocalpv_test.go +++ b/pkg/storage/zfslocalpv_test.go @@ -115,7 +115,7 @@ func TestDescribeZFSNode(t *testing.T) { }, { "two ZFS node exist, none asked for", - args{c: &client.K8sClient{Ns: "zfs", ZFCS: fakezfsclient.NewSimpleClientset(&zfsNode1, &zfsNode3)}, sName: "cstor-pool-name"}, + args{c: &client.K8sClient{Ns: "zfs", ZFCS: fakezfsclient.NewSimpleClientset(&zfsNode1, &zfsNode3)}, sName: "some-pool-name"}, true, }, } diff --git a/pkg/upgrade/api.go b/pkg/upgrade/api.go deleted file mode 100644 index 98f8b7f8..00000000 --- a/pkg/upgrade/api.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package upgrade - -import ( - "fmt" - - corebuilder "github.com/openebs/api/v2/pkg/kubernetes/core" - batchV1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" -) - -type Job struct { - *batchV1.Job -} - -// NewJob returns an empty instance of BatchJob -func NewJob() *Job { - return &Job{ - &batchV1.Job{}, - } -} - -// WithName sets the name of the field of Job -func (b *Job) WithName(name string) *Job { - b.Name = name - return b -} - -// WithGeneratedName Creates a job with auto-generated name -func (b *Job) WithGeneratedName(name string) *Job { - b.GenerateName = fmt.Sprintf("%s-", name) - return b -} - -// WithLabel sets label for the job -func (b *Job) WithLabel(label map[string]string) *Job { - b.Labels = label - return b -} - -// WithNamespace sets the namespace of the Job -func (b *Job) WithNamespace(namespace string) *Job { - b.Namespace = namespace - return b -} - -// BuildJobSpec builds an empty Job Spec -func (b *Job) BuildJobSpec() *Job { - b.Spec = batchV1.JobSpec{} - return b -} - -// WithBackOffLimit sets the backOffLimit for pods in the Job with given value -func (b *Job) WithBackOffLimit(limit int32) *Job { - b.Spec.BackoffLimit = &limit - return b -} - -// WithPodTemplateSpec sets the template Field for Job -func (b *Job) WithPodTemplateSpec(pts *corebuilder.PodTemplateSpec) *Job { - templateSpecObj := pts.Build() - b.Spec.Template = *templateSpecObj - return b -} - -// Temporary code until PR into openebs/api is not merged---- -func (b *Job) WithRestartPolicy(policy corev1.RestartPolicy) *Job { - b.Spec.Template.Spec.RestartPolicy = policy - return b -} diff --git a/pkg/upgrade/cstor.go b/pkg/upgrade/cstor.go deleted file mode 100644 index d2c1c468..00000000 --- a/pkg/upgrade/cstor.go +++ /dev/null @@ -1,165 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package upgrade - -import ( - "errors" - "fmt" - "log" - - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/kubernetes/core" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - batchV1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" -) - -func InstantiateCspcUpgrade(options UpgradeOpts) { - k := client.NewK8sClient() - - // auto-determine cstor namespace - var err error - k.Ns, err = k.GetOpenEBSNamespace(util.CstorCasType) - if err != nil { - fmt.Println(`Error determining cstor namespace! using "openebs" as namespace`) - k.Ns = "openebs" - } - - cspcList, err := k.ListCSPC() - if err != nil { - log.Fatal("err listing CSPC ", err) - } - - poolNames := getCSPCPoolNames(cspcList) - cfg := UpgradeJobCfg{ - fromVersion: "", - toVersion: "", - namespace: k.Ns, - resources: poolNames, - serviceAccountName: "", - backOffLimit: 4, - logLevel: 4, - additionalArgs: addArgs(options), - } - - cfg.fromVersion, cfg.toVersion, err = getCstorVersionDetails(cspcList) - if err != nil { - fmt.Println("error: ", err) - } - if options.ToVersion != "" { // overriding the desired version from the cli flag - cfg.toVersion = options.ToVersion - } - - cfg.serviceAccountName = GetCSPCOperatorServiceAccName(k) - - // Check if a job is running with underlying PV - err = inspectRunningUpgradeJobs(k, &cfg) - // If error or upgrade job is already running return - if err != nil { - log.Fatal("An upgrade job is already running with the underlying volume!, More: ", err) - } - - // Create upgrade job - k.CreateBatchJob(buildCspcbatchJob(&cfg), k.Ns) -} - -// buildCspcbatchJob returns CSPC Job to be build -func buildCspcbatchJob(cfg *UpgradeJobCfg) *batchV1.Job { - return NewJob(). - WithGeneratedName("cstor-cspc-upgrade"). - WithLabel(map[string]string{"name": "cstor-cspc-upgrade", "cas-type": "cstor"}). // sets labels for job discovery - WithNamespace(cfg.namespace). - WithBackOffLimit(cfg.backOffLimit). - WithPodTemplateSpec( - func() *core.PodTemplateSpec { - return core.NewPodTemplateSpec(). - WithServiceAccountName(cfg.serviceAccountName). - WithContainers( - func() *core.Container { - return core.NewContainer(). - WithName("upgrade-cstor-cspc-go"). - WithArgumentsNew(getCstorCspcContainerArgs(cfg)). - WithEnvsNew( - []corev1.EnvVar{ - { - Name: "OPENEBS_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "metadata.namespace", - }, - }, - }, - }, - ). - WithImage(fmt.Sprintf("openebs/upgrade:%s", cfg.toVersion)). - WithImagePullPolicy(corev1.PullIfNotPresent) // Add TTY to openebs/api - }(), - ) - }(), - ). - WithRestartPolicy(corev1.RestartPolicyOnFailure). // Add restart policy in openebs/api - Job -} - -func getCstorCspcContainerArgs(cfg *UpgradeJobCfg) []string { - // Set container arguments - args := append([]string{ - "cstor-cspc", - fmt.Sprintf("--from-version=%s", cfg.fromVersion), - fmt.Sprintf("--to-version=%s", cfg.toVersion), - "--v=4", // can be taken from flags - }, cfg.resources...) - args = append(args, cfg.additionalArgs...) - return args -} - -func getCSPCPoolNames(cspcList *cstorv1.CStorPoolClusterList) []string { - var poolNames []string - for _, cspc := range cspcList.Items { - poolNames = append(poolNames, cspc.Name) - } - - return poolNames -} - -// getCstorVersionDetails returns cstor versioning details for upgrade job cfg -// It returns fromVersion, toVersion, or error -func getCstorVersionDetails(cspcList *cstorv1.CStorPoolClusterList) (fromVersion string, toVersion string, err error) { - fmt.Println("Fetching CSPC control plane and Data Plane Version") - for _, cspc := range cspcList.Items { - fromVersion = cspc.VersionDetails.Status.Current - toVersion = cspc.VersionDetails.Desired - - if fromVersion != "" && toVersion != "" { - fmt.Println("Current Version:", fromVersion) - fmt.Println("Desired Version:", toVersion) - return - } - } - - return "", "", errors.New("problems fetching versioning details") -} - -func GetCSPCOperatorServiceAccName(k *client.K8sClient) string { - pods, err := k.GetPods("openebs.io/component-name=cspc-operator", "", k.Ns) - if err != nil || len(pods.Items) == 0 { - log.Fatal("error occurred while searching operator, or no operator is found: ", err) - } - - return pods.Items[0].Spec.ServiceAccountName -} diff --git a/pkg/upgrade/jiva.go b/pkg/upgrade/jiva.go deleted file mode 100644 index 1ac91232..00000000 --- a/pkg/upgrade/jiva.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package upgrade - -import ( - "fmt" - "log" - - core "github.com/openebs/api/v2/pkg/kubernetes/core" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - batchV1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" -) - -type jobInfo struct { - name string - namespace string -} - -// Jiva Data-plane Upgrade Job instantiator -func InstantiateJivaUpgrade(upgradeOpts UpgradeOpts) { - k := client.NewK8sClient() - - // auto-determine jiva namespace - ns, err := k.GetOpenEBSNamespace(util.JivaCasType) - if err != nil { - fmt.Println(`Error determining namespace! using "openebs" as namespace`) - ns = "openebs" - } - - // get running volumes from cluster - volNames, fromVersion, err := getJivaVolumesVersion(k) - if err != nil { - fmt.Println(err) - return - } - - // assign to-version - if upgradeOpts.ToVersion == "" { - pods, e := k.GetPods("name=jiva-operator", "", "") - if e != nil { - fmt.Println("Failed to get operator-version, err: ", e) - return - } - - if len(pods.Items) == 0 { - fmt.Println("Jiva-operator is not running!") - return - } - - upgradeOpts.ToVersion = pods.Items[0].Labels["openebs.io/version"] - upgradeOpts.ServiceAccountName = getServiceAccountName(pods) - } - - // create configuration - cfg := UpgradeJobCfg{ - fromVersion: fromVersion, - toVersion: upgradeOpts.ToVersion, - namespace: ns, - resources: volNames, - serviceAccountName: upgradeOpts.ServiceAccountName, - backOffLimit: 4, - logLevel: 4, - additionalArgs: addArgs(upgradeOpts), - } - - // Check if a job is running with underlying PV - err = inspectRunningUpgradeJobs(k, &cfg) - // If error or upgrade job is already running return - if err != nil { - log.Fatal("An upgrade job is already running with the underlying volume!, More: ", err) - } - - k.CreateBatchJob(BuildJivaBatchJob(&cfg), cfg.namespace) -} - -// getJivaVolumesVersion returns the Jiva volumes list and current version -func getJivaVolumesVersion(k *client.K8sClient) ([]string, string, error) { - // 1. Fetch all jivavolumes CRs in all namespaces - _, jvMap, err := k.GetJVs(nil, util.Map, "", util.MapOptions{Key: util.Name}) - if err != nil { - return nil, "", fmt.Errorf("err getting jiva volumes: %s", err.Error()) - } - - var jivaList *corev1.PersistentVolumeList - //2. Get Jiva Persistent volumes - jivaList, err = k.GetPvByCasType([]string{"jiva"}, "") - if err != nil { - return nil, "", fmt.Errorf("err getting jiva volumes: %s", err.Error()) - } - - var volumeNames []string - var version string - - //3. Write-out names, versions and desired-versions - for _, pv := range jivaList.Items { - volumeNames = append(volumeNames, pv.Name) - if v, ok := jvMap[pv.Name]; ok && len(version) == 0 { - version = v.VersionDetails.Status.Current - } - } - - //4. Check for zero jiva-volumes - if len(version) == 0 || len(volumeNames) == 0 { - return volumeNames, version, fmt.Errorf("no jiva volumes found") - } - - return volumeNames, version, nil -} - -// BuildJivaBatchJob returns Job to be build -func BuildJivaBatchJob(cfg *UpgradeJobCfg) *batchV1.Job { - return NewJob(). - WithGeneratedName("jiva-upgrade"). - WithLabel(map[string]string{"name": "jiva-upgrade", "cas-type": "jiva"}). // sets labels for job discovery - WithNamespace(cfg.namespace). - WithBackOffLimit(cfg.backOffLimit). - WithPodTemplateSpec( - func() *core.PodTemplateSpec { - return core.NewPodTemplateSpec(). - WithServiceAccountName(cfg.serviceAccountName). - WithContainers( - func() *core.Container { - return core.NewContainer(). - WithName("upgrade-jiva-go"). - WithArgumentsNew(getJivaContainerArguments(cfg)). - WithEnvsNew( - []corev1.EnvVar{ - { - Name: "OPENEBS_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "metadata.namespace", - }, - }, - }, - }, - ). - WithImage(fmt.Sprintf("openebs/upgrade:%s", cfg.toVersion)). - WithImagePullPolicy(corev1.PullIfNotPresent) // Add TTY to openebs/api - }(), - ) - }(), - ). - WithRestartPolicy(corev1.RestartPolicyOnFailure). // Add restart policy in openebs/api - Job -} - -func getJivaContainerArguments(cfg *UpgradeJobCfg) []string { - // Set container arguments - args := append([]string{ - "jiva-volume", - fmt.Sprintf("--from-version=%s", cfg.fromVersion), - fmt.Sprintf("--to-version=%s", cfg.toVersion), - "--v=4", // can be taken from flags - }, cfg.resources...) - args = append(args, cfg.additionalArgs...) - return args -} diff --git a/pkg/upgrade/status/jiva.go b/pkg/upgrade/status/jiva.go deleted file mode 100644 index 31298ee3..00000000 --- a/pkg/upgrade/status/jiva.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package status - -import ( - "fmt" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" -) - -// Get job with the name -> apply selector to pod -func GetJobStatus(namespace string) { - k := client.NewK8sClient() - k.Ns = namespace - // get jiva-upgrade batch jobs - joblist, err := k.GetBatchJobs(namespace, "cas-type=jiva,name=jiva-upgrade") - if err != nil { - fmt.Println("Error getting jiva-upgrade jobs:", err) - return - } - - // No jobs found - if len(joblist.Items) == 0 { - fmt.Printf("No upgrade-jobs Found in %s namespace", namespace) - return - } - - for _, job := range joblist.Items { - fmt.Println("***************************************") - fmt.Println("Job Name: ", job.Name) - getPodLogs(k, job.Name, namespace) - } - fmt.Println("***************************************") -} - -// Get all the logs from the pods associated with a job -func getPodLogs(k *client.K8sClient, name string, namespace string) { - // get pods created by the job - podList, err := k.GetPods(fmt.Sprintf("job-name=%s", name), "", namespace) - if err != nil { - printColoredText(fmt.Sprintf("error getting pods of job %s, err: %s", name, err), util.Red) - return - } - - // range over pods to get all the logs - for _, pod := range podList.Items { - fmt.Println("From Pod:", pod.Name) - logs := k.GetPodLogs(pod.Name, namespace) - if logs == "" { - fmt.Printf("-> No recent logs from the pod") - fmt.Println() - continue - } - printColoredText(logs, util.Blue) - } - - if len(podList.Items) == 0 { - printColoredText("No pods are running for this job", util.Red) - } -} - -func printColoredText(message string, color util.Color) { - fmt.Println(util.ColorText(message, color)) -} diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go deleted file mode 100644 index 24e7c9ea..00000000 --- a/pkg/upgrade/upgrade.go +++ /dev/null @@ -1,206 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package upgrade - -import ( - "fmt" - "os" - "time" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - batchV1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" -) - -// UpgradeOpts are the upgrade options that are provided -// with the CLI flags -type UpgradeOpts struct { - CasType string - ToVersion string - ImagePrefix string - ImageTag string - ServiceAccountName string -} - -// UpgradeJobCfg holds upgrade job confiogurations while creating a new Job -type UpgradeJobCfg struct { - fromVersion string - toVersion string - namespace string - resources []string - backOffLimit int32 - serviceAccountName string - logLevel int32 - additionalArgs []string -} - -// inspectRunningUpgradeJobs inspects all the jobs running in the cluster -// and returns if even one of the the jobs updating the resource is already scheduled/running -func inspectRunningUpgradeJobs(k *client.K8sClient, cfg *UpgradeJobCfg) error { - jobs, err := k.GetBatchJobs("", "") - if err != nil { - return err - } - - // runningJob holds the information about the jobs that are in use by the PV - // that has an upgrade-job progress(any status) already going - // This anonynomous function is used to ease-in the code logic to prevent - // using multiple booleans to get out of the loops once needed to exit - // return statement in anonymous functions helps us with preventing additional checks - var runningJob *batchV1.Job - func() { - for _, job := range jobs.Items { // JobItems - for _, pvName := range cfg.resources { // running pvs in control plane - for _, container := range job.Spec.Template.Spec.Containers { // iterate on containers provided by the cfg - for _, args := range container.Args { // check if the running jobs (PVs) and the upcoming job(PVs) are common - if args == pvName { - runningJob = &job - return - } - } - } - } - } - }() - - return runningJobHandler(k, runningJob) -} - -// runningJobHandler checks the status of the job and takes action on it -// to modify or delete it based on the status of the Job -func runningJobHandler(k *client.K8sClient, runningJob *batchV1.Job) error { - - if runningJob != nil { - jobCondition := runningJob.Status.Conditions - info := jobInfo{name: runningJob.Name, namespace: runningJob.Namespace} - if runningJob.Status.Failed > 0 || - len(jobCondition) > 0 && jobCondition[0].Type == "Failed" && jobCondition[0].Status == "True" { - fmt.Println("Previous job failed.") - fmt.Println("Reason: ", getReason(runningJob)) - fmt.Println("Creating a new Job with name:", info.name) - // Job found thus delete the job and return false so that further process can be started - if err := startDeletionTask(k, &info); err != nil { - fmt.Println("error deleting job:", err) - return err - } - } - - if runningJob.Status.Active > 0 { - fmt.Println("A job is already active with the name", runningJob.Name, " that is upgrading the PV.") - // TODO: Check the POD underlying the PV if their is any error inside - os.Exit(0) - } - - if runningJob.Status.Succeeded > 0 { - fmt.Println("Previous upgrade-job was successful for upgrading P.V.") - return shouldRestartJob(k, info) - } - } - - return nil -} - -// getReason returns the reason for the current status of Job -func getReason(job *batchV1.Job) string { - reason := job.Status.Conditions[0].Reason - if len(reason) == 0 { - return "Reason Not Found, check by inspecting jobs" - } - return reason -} - -// startDeletionTask instantiates a deletion process -func startDeletionTask(k *client.K8sClient, info *jobInfo) error { - err := k.DeleteBatchJob(info.name, info.namespace) - if err != nil { - return err - } - confirmDeletion(k, info) - return nil -} - -// confirmDeletion runs until the job is successfully done or reached threshold duration -func confirmDeletion(k *client.K8sClient, info *jobInfo) { - // create interval to call function periodically - interval := time.NewTicker(time.Second * 2) - - // Create channel - channel := make(chan bool) - - // Set threshold time - go func() { - time.Sleep(time.Second * 10) - channel <- true - }() - - for { - select { - case <-interval.C: - _, err := k.GetBatchJob(info.name, info.namespace) - // Job is deleted successfully - if err != nil { - return - } - case <-channel: - fmt.Println("Waiting time reached! Try Again!") - return - } - } -} - -// Returns additional arguments like image-prefix and image-tags -func addArgs(upgradeOpts UpgradeOpts) []string { - var result []string - if upgradeOpts.ImagePrefix != "" { - result = append(result, fmt.Sprintf("--to-version-image-prefix=%s", upgradeOpts.ImagePrefix)) - } - - if upgradeOpts.ImageTag != "" { - result = append(result, fmt.Sprintf("--to-version-image-tag=%s", upgradeOpts.ImageTag)) - } - - return result -} - -// getServiceAccountName returns service account Name for the openEBS resource -func getServiceAccountName(podList *corev1.PodList) string { - var serviceAccountName string - for _, pod := range podList.Items { - svname := pod.Spec.ServiceAccountName - if svname != "" { - serviceAccountName = svname - } - } - return serviceAccountName -} - -// shouldRestartJob prompts if the job should be restarted after deleting -// the traces of previous one -func shouldRestartJob(k *client.K8sClient, info jobInfo) error { - // Provide the option to restart the Job - shouldStart := util.PromptToStartAgain("Do you want to restart the Job?(no)", false) - if shouldStart { - // Delete previous successful task - if err := startDeletionTask(k, &info); err != nil { - return err - } - } else { - os.Exit(0) - } - - return nil -} diff --git a/pkg/util/checks.go b/pkg/util/checks.go index 079bf1c5..42b5fd72 100644 --- a/pkg/util/checks.go +++ b/pkg/util/checks.go @@ -17,19 +17,9 @@ limitations under the License. package util import ( - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - corev1 "k8s.io/api/core/v1" ) -// CheckVersion returns a message based on the status of the version -func CheckVersion(versionDetail v1.VersionDetails) string { - if string(versionDetail.Status.State) == "Reconciled" || string(versionDetail.Status.State) == "" { - return versionDetail.Status.Current - } - return string(versionDetail.Status.State) + ", desired version " + versionDetail.Desired -} - // CheckForVol is used to check if the we can get the volume, if no volume attachment // to SC for the corresponding volume is found display error func CheckForVol(name string, vols map[string]*Volume) *Volume { @@ -46,7 +36,7 @@ func CheckForVol(name string, vols map[string]*Volume) *Volume { return errVol } -//AccessModeToString Flattens the arrat of AccessModes and returns a string fit to display in the output +// AccessModeToString Flattens the arrat of AccessModes and returns a string fit to display in the output func AccessModeToString(accessModeArray []corev1.PersistentVolumeAccessMode) string { accessModes := "" for _, mode := range accessModeArray { diff --git a/pkg/util/checks_test.go b/pkg/util/checks_test.go index 8783a1dc..8ecf277b 100644 --- a/pkg/util/checks_test.go +++ b/pkg/util/checks_test.go @@ -17,42 +17,11 @@ limitations under the License. package util import ( - "reflect" "testing" corev1 "k8s.io/api/core/v1" ) -func TestCheckForVol(t *testing.T) { - type args struct { - name string - vols map[string]*Volume - } - tests := []struct { - name string - args args - want *Volume - }{ - { - "volume_attached_to_storage_class", - args{name: "cstor_volume", vols: map[string]*Volume{"cstor_volume": {CSIVolumeAttachmentName: "volume_one"}}}, - &Volume{CSIVolumeAttachmentName: "volume_one"}, - }, - { - "volume_not_attached_to_storage_class", - args{name: "cstor_volume", vols: map[string]*Volume{"cstor_volume_two": {CSIVolumeAttachmentName: "volume_one"}}}, - &Volume{StorageClass: NotAvailable, Node: NotAvailable, AttachementStatus: NotAvailable, AccessMode: NotAvailable}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := CheckForVol(tt.args.name, tt.args.vols); !reflect.DeepEqual(got, tt.want) { - t.Errorf("CheckForVol() = %v, want %v", got, tt.want) - } - }) - } -} - func TestAccessModeToString(t *testing.T) { type args struct { accessModeArray []corev1.PersistentVolumeAccessMode diff --git a/pkg/util/constant.go b/pkg/util/constant.go index bbc526da..df2c23ba 100644 --- a/pkg/util/constant.go +++ b/pkg/util/constant.go @@ -25,46 +25,22 @@ const ( Unknown = "unknown" // OpenEBSCasTypeKeySc present in parameter of SC OpenEBSCasTypeKeySc = "cas-type" - // CstorCasType cas type name - CstorCasType = "cstor" // ZFSCasType cas type name ZFSCasType = "localpv-zfs" - // JivaCasType is the cas type name for Jiva - JivaCasType = "jiva" // LVMCasType cas type name LVMCasType = "localpv-lvm" // LocalPvHostpathCasType cas type name LocalPvHostpathCasType = "localpv-hostpath" - // LocalDeviceCasType cas type name - LocalDeviceCasType = "localpv-device" // LocalHostpathCasLabel cas-type label in dynamic-localpv-provisioner LocalHostpathCasLabel = "local-hostpath" - // Healthy cstor volume status - Healthy = "Healthy" // StorageKey key present in pvc status.capacity StorageKey = "storage" // NotAvailable shows something is missing, could be a component, // unknown version, or some other unknowns NotAvailable = "N/A" - // CVAVolnameKey present in label of CVA - CVAVolnameKey = "Volname" - // UnicodeCross stores the character representation of U+2718 - UnicodeCross = "✘" - // UnicodeCheck stores the character representation of U+2714 - UnicodeCheck = "✔" - // NotFound stores the Not Found Status - NotFound = "Not Found" - // CVANotAttached stores CVA Not Attached status - CVANotAttached = "Not Attached to Application" - // Attached stores CVA Attached Status - Attached = "Attached" ) const ( - // CStorCSIDriver is the name of CStor CSI driver - CStorCSIDriver = "cstor.csi.openebs.io" - // JivaCSIDriver is the name of the Jiva CSI driver - JivaCSIDriver = "jiva.csi.openebs.io" // ZFSCSIDriver is the name of the ZFS localpv CSI driver ZFSCSIDriver = "zfs.csi.openebs.io" // LocalPVLVMCSIDriver is the name of the LVM LocalPV CSI driver @@ -74,10 +50,6 @@ const ( // Constant CSI component-name label values const ( - // CStorCSIControllerLabelValue is the label value of CSI controller STS & pod - CStorCSIControllerLabelValue = "openebs-cstor-csi-controller" - // JivaCSIControllerLabelValue is the label value of CSI controller STS & pod - JivaCSIControllerLabelValue = "openebs-jiva-csi-controller" // LVMLocalPVcsiControllerLabelValue is the label value of CSI controller STS & pod LVMLocalPVcsiControllerLabelValue = "openebs-lvm-controller" // ZFSLocalPVcsiControllerLabelValue is the label value of CSI controller STS & pod @@ -85,12 +57,6 @@ const ( ) const ( - // CstorComponentNames for the cstor control plane components - CstorComponentNames = "cspc-operator,cvc-operator,cstor-admission-webhook,openebs-cstor-csi-node,openebs-cstor-csi-controller" - // NDMComponentNames for the ndm components - NDMComponentNames = "openebs-ndm-operator,ndm" - // JivaComponentNames for the jiva control plane components - JivaComponentNames = "openebs-jiva-csi-node,openebs-jiva-csi-controller,jiva-operator" // LVMComponentNames for the lvm control plane components LVMComponentNames = "openebs-lvm-controller,openebs-lvm-node" // ZFSComponentNames for the zfs control plane components @@ -101,36 +67,24 @@ const ( var ( // CasTypeAndComponentNameMap stores the component name of the corresponding cas type - // NOTE: Not including ZFSLocalPV as it'd break existing code CasTypeAndComponentNameMap = map[string]string{ - CstorCasType: CStorCSIControllerLabelValue, - JivaCasType: JivaCSIControllerLabelValue, LVMCasType: LVMLocalPVcsiControllerLabelValue, ZFSCasType: ZFSLocalPVcsiControllerLabelValue, LocalPvHostpathCasType: HostpathComponentNames, } // ComponentNameToCasTypeMap is a reverse map of CasTypeAndComponentNameMap - // NOTE: Not including ZFSLocalPV as it'd break existing code ComponentNameToCasTypeMap = map[string]string{ - CStorCSIControllerLabelValue: CstorCasType, - JivaCSIControllerLabelValue: JivaCasType, LVMLocalPVcsiControllerLabelValue: LVMCasType, ZFSLocalPVcsiControllerLabelValue: ZFSCasType, HostpathComponentNames: LocalPvHostpathCasType, } // ProvsionerAndCasTypeMap stores the cas type name of the corresponding provisioner ProvsionerAndCasTypeMap = map[string]string{ - CStorCSIDriver: CstorCasType, - JivaCSIDriver: JivaCasType, - // NOTE: In near future this might mean all local-pv volumes LocalPVLVMCSIDriver: LVMCasType, ZFSCSIDriver: ZFSCasType, } // CasTypeToCSIProvisionerMap stores the provisioner of corresponding cas-types CasTypeToCSIProvisionerMap = map[string]string{ - CstorCasType: CStorCSIDriver, - JivaCasType: JivaCSIDriver, - // NOTE: In near future this might mean all local-pv volumes LVMCasType: LocalPVLVMCSIDriver, ZFSCasType: ZFSCSIDriver, } @@ -138,43 +92,10 @@ var ( // CasTypeToComponentNamesMap stores the names of the control-plane components of each cas-types. // To show statuses of new CasTypes, please update this map. CasTypeToComponentNamesMap = map[string]string{ - CstorCasType: CstorComponentNames + "," + NDMComponentNames, - JivaCasType: JivaComponentNames + "," + HostpathComponentNames, LocalPvHostpathCasType: HostpathComponentNames, - LocalDeviceCasType: HostpathComponentNames + "," + NDMComponentNames, ZFSCasType: ZFSComponentNames, LVMCasType: LVMComponentNames, } - - // CstorReplicaColumnDefinations stores the Table headers for CVR Details - CstorReplicaColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "ZFS Used(compressed)", Type: "string"}, - {Name: "LogicalReferenced", Type: "string"}, - {Name: "Status", Type: "string"}, - {Name: "Age", Type: "string"}, - } - // PodDetailsColumnDefinations stores the Table headers for Pod Details - PodDetailsColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Namespace", Type: "string"}, - {Name: "Name", Type: "string"}, - {Name: "Ready", Type: "string"}, - {Name: "Status", Type: "string"}, - {Name: "Age", Type: "string"}, - {Name: "IP", Type: "string"}, - {Name: "Node", Type: "string"}, - } - // JivaPodDetailsColumnDefinations stores the Table headers for Jiva Pod Details - JivaPodDetailsColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Namespace", Type: "string"}, - {Name: "Name", Type: "string"}, - {Name: "Mode", Type: "string"}, - {Name: "Node", Type: "string"}, - {Name: "Status", Type: "string"}, - {Name: "IP", Type: "string"}, - {Name: "Ready", Type: "string"}, - {Name: "Age", Type: "string"}, - } // VolumeListColumnDefinations stores the Table headers for Volume Details VolumeListColumnDefinations = []metav1.TableColumnDefinition{ {Name: "Namespace", Type: "string"}, @@ -187,66 +108,6 @@ var ( {Name: "Access Mode", Type: "string"}, {Name: "Attached Node", Type: "string"}, } - // CstorPoolListColumnDefinations stores the Table headers for Cstor Pool Details - CstorPoolListColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "HostName", Type: "string"}, - {Name: "Free", Type: "string"}, - {Name: "Capacity", Type: "string"}, - {Name: "Read Only", Type: "bool"}, - {Name: "Provisioned Replicas", Type: "int"}, - {Name: "Healthy Replicas", Type: "int"}, - {Name: "Status", Type: "string"}, - {Name: "Age", Type: "string"}, - } - // BDListColumnDefinations stores the Table headers for Block Device Details - BDListColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Capacity", Type: "string"}, - {Name: "State", Type: "string"}, - } - // PoolReplicaColumnDefinations stores the Table headers for Pool Replica Details - PoolReplicaColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "PVC Name", Type: "string"}, - {Name: "Size", Type: "string"}, - {Name: "State", Type: "string"}, - } - // CstorBackupColumnDefinations stores the Table headers for Cstor Backup Details - CstorBackupColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Backup Name", Type: "string"}, - {Name: "Volume Name", Type: "string"}, - {Name: "Backup Destination", Type: "string"}, - {Name: "Snap Name", Type: "string"}, - {Name: "Status", Type: "string"}, - } - // CstorCompletedBackupColumnDefinations stores the Table headers for Cstor Completed Backup Details - CstorCompletedBackupColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Backup Name", Type: "string"}, - {Name: "Volume Name", Type: "string"}, - {Name: "Last Snap Name", Type: "string"}, - } - // CstorRestoreColumnDefinations stores the Table headers for Cstor Restore Details - CstorRestoreColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Restore Name", Type: "string"}, - {Name: "Volume Name", Type: "string"}, - {Name: "Restore Source", Type: "string"}, - {Name: "Storage Class", Type: "string"}, - {Name: "Status", Type: "string"}, - } - // BDTreeListColumnDefinations stores the Table headers for Block Device Details, when displayed as tree - BDTreeListColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Path", Type: "string"}, - {Name: "Size", Type: "string"}, - {Name: "ClaimState", Type: "string"}, - {Name: "Status", Type: "string"}, - {Name: "FsType", Type: "string"}, - {Name: "MountPoint", Type: "string"}, - } // LVMvolgroupListColumnDefinitions stores the table headers for listing lvm vg-group when displayed as tree LVMvolgroupListColumnDefinitions = []metav1.TableColumnDefinition{ {Name: "Name", Type: "string"}, @@ -259,38 +120,6 @@ var ( {Name: "FreeSize", Type: "string"}, } - // JivaReplicaPVCColumnDefinations stores the Table headers for Jiva Replica PVC details - JivaReplicaPVCColumnDefinations = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Status", Type: "string"}, - {Name: "Volume", Type: "string"}, - {Name: "Capacity", Type: "string"}, - {Name: "Storageclass", Type: "string"}, - {Name: "Age", Type: "string"}, - } - - // CstorVolumeCRStatusColumnDefinitions stores the Table headers for Cstor CRs status details - CstorVolumeCRStatusColumnDefinitions = []metav1.TableColumnDefinition{ - {Name: "Kind", Type: "string"}, - {Name: "Name", Type: "string"}, - {Name: "Status", Type: "string"}, - } - - // VolumeTotalAndUsageDetailColumnDefinitions stores the Table headers for volume usage details - VolumeTotalAndUsageDetailColumnDefinitions = []metav1.TableColumnDefinition{ - {Name: "Total Capacity", Type: "string"}, - {Name: "Used Capacity", Type: "string"}, - {Name: "Available Capacity", Type: "string"}, - } - // EventsColumnDefinitions stores the Table headers for events details - EventsColumnDefinitions = []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string"}, - {Name: "Action", Type: "string"}, - {Name: "Reason", Type: "string"}, - {Name: "Message", Type: "string"}, - {Name: "Type", Type: "string"}, - } - VersionColumnDefinition = []metav1.TableColumnDefinition{ {Name: "Component", Type: "string"}, {Name: "Version", Type: "string"}, diff --git a/pkg/util/error_test.go b/pkg/util/error_test.go index c9463d44..8222adc8 100644 --- a/pkg/util/error_test.go +++ b/pkg/util/error_test.go @@ -83,9 +83,9 @@ func TestHandleEmptyTableError(t *testing.T) { args{ resource: "ResourceType", ns: "InValid", - casType: "jiva", + casType: "zfs", }, - fmt.Errorf("no jiva ResourceType found in InValid namespace"), + fmt.Errorf("no zfs ResourceType found in InValid namespace"), }, { "", diff --git a/pkg/util/k8s_utils.go b/pkg/util/k8s_utils.go index ec1238f6..090e0964 100644 --- a/pkg/util/k8s_utils.go +++ b/pkg/util/k8s_utils.go @@ -19,21 +19,10 @@ package util import ( "strconv" - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/storage/v1" ) -// GetUsedCapacityFromCVR as the healthy replicas would have the correct used capacity details -func GetUsedCapacityFromCVR(cvrList *cstorv1.CStorVolumeReplicaList) string { - for _, item := range cvrList.Items { - if item.Status.Phase == Healthy { - return item.Status.Capacity.Used - } - } - return "" -} - // GetCasType from the v1pv and v1sc, this is a fallback checker method, it checks // both the resource only if the castype is not found. func GetCasType(v1PV *corev1.PersistentVolume, v1SC *v1.StorageClass) string { @@ -106,5 +95,5 @@ func GetReadyContainers(containers []corev1.ContainerStatus) string { // IsValidCasType to return true if the casType is supported func IsValidCasType(casType string) bool { - return casType == CstorCasType || casType == JivaCasType || casType == LVMCasType || casType == ZFSCasType + return casType == LVMCasType || casType == ZFSCasType } diff --git a/pkg/util/k8s_utils_test.go b/pkg/util/k8s_utils_test.go index 45d03d74..ed8a0b13 100644 --- a/pkg/util/k8s_utils_test.go +++ b/pkg/util/k8s_utils_test.go @@ -19,181 +19,9 @@ package util import ( "testing" - cstorv1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/storage/v1" ) -var ( - inAnnotationPV, inLabelsPV = cstorPV1, cstorPV1 -) - -func TestGetCasType(t *testing.T) { - inAnnotationPV.Annotations = map[string]string{"openebs.io/cas-type": "cstor"} - inLabelsPV.Labels = map[string]string{"openebs.io/cas-type": "cstor"} - type args struct { - v1PV *corev1.PersistentVolume - v1SC *v1.StorageClass - } - tests := []struct { - name string - args args - want string - }{ - { - "PV and SC both present", - args{ - v1PV: &cstorPV1, - v1SC: &cstorSC, - }, - "cstor", - }, - { - "PV present and SC absent", - args{ - v1PV: &cstorPV1, - v1SC: nil, - }, - "cstor", - }, - { - "PV present and SC absent", - args{ - v1PV: &inLabelsPV, - v1SC: nil, - }, - "cstor", - }, - { - "PV present and SC absent", - args{ - v1PV: &inAnnotationPV, - v1SC: nil, - }, - "cstor", - }, - { - "PV absent and SC present", - args{ - v1PV: nil, - v1SC: &cstorSC, - }, - "cstor", - }, - { - "PV absent and SC present", - args{ - v1PV: nil, - v1SC: &jivaSC, - }, - "jiva", - }, - { - "Both PV and SC absent", - args{ - v1PV: nil, - v1SC: nil, - }, - "unknown", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetCasType(tt.args.v1PV, tt.args.v1SC); got != tt.want { - t.Errorf("GetCasType() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetCasTypeFromPV(t *testing.T) { - inAnnotationPV.Annotations = map[string]string{"openebs.io/cas-type": "cstor"} - inLabelsPV.Labels = map[string]string{"openebs.io/cas-type": "cstor"} - type args struct { - v1PV *corev1.PersistentVolume - } - tests := []struct { - name string - args args - want string - }{ - { - "From volume attributes", - args{ - v1PV: &cstorPV1, - }, - "cstor", - }, - { - "From labels", - args{ - v1PV: &inLabelsPV, - }, - "cstor", - }, - { - "From annotations", - args{ - v1PV: &inAnnotationPV, - }, - "cstor", - }, - { - "nil pv", - args{ - v1PV: nil, - }, - "unknown", - }, - { - "zfs pv, from CSI driver", - args{v1PV: &zfspv}, - "localpv-zfs", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetCasTypeFromPV(tt.args.v1PV); got != tt.want { - t.Errorf("GetCasTypeFromPV() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetCasTypeFromSC(t *testing.T) { - type args struct { - v1SC *v1.StorageClass - } - tests := []struct { - name string - args args - want string - }{ - { - "From provisioner", - args{v1SC: &cstorSC}, - "cstor", - }, - { - "From parameters", - args{v1SC: &jivaSC}, - "jiva", - }, - { - "SC nil", - args{v1SC: nil}, - "unknown", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetCasTypeFromSC(tt.args.v1SC); got != tt.want { - t.Errorf("GetCasTypeFromSC() = %v, want %v", got, tt.want) - } - }) - } -} - func TestGetReadyContainers(t *testing.T) { type args struct { containers []corev1.ContainerStatus @@ -223,52 +51,6 @@ func TestGetReadyContainers(t *testing.T) { } } -func TestGetUsedCapacityFromCVR(t *testing.T) { - type args struct { - cvrList *cstorv1.CStorVolumeReplicaList - } - tests := []struct { - name string - args args - want string - }{ - { - "Valid values", - args{cvrList: &cstorv1.CStorVolumeReplicaList{Items: []cstorv1.CStorVolumeReplica{{Status: cstorv1.CStorVolumeReplicaStatus{ - Phase: "Init", - Capacity: cstorv1.CStorVolumeReplicaCapacityDetails{Total: "5.0 GiB", Used: "2.1 GiB"}, - }}, {Status: cstorv1.CStorVolumeReplicaStatus{ - Phase: "Healthy", - Capacity: cstorv1.CStorVolumeReplicaCapacityDetails{Total: "5.0 GiB", Used: "2.5 GiB"}, - }}}}}, - "2.5 GiB", - }, - { - "Valid values", - args{cvrList: &cstorv1.CStorVolumeReplicaList{Items: []cstorv1.CStorVolumeReplica{{Status: cstorv1.CStorVolumeReplicaStatus{ - Phase: "Init", - Capacity: cstorv1.CStorVolumeReplicaCapacityDetails{Total: "5.0 GiB", Used: "2.5 GiB"}, - }}, {Status: cstorv1.CStorVolumeReplicaStatus{ - Phase: "Init", - Capacity: cstorv1.CStorVolumeReplicaCapacityDetails{Total: "5.0 GiB", Used: "2.5 GiB"}, - }}}}}, - "", - }, - { - "Valid values", - args{cvrList: &cstorv1.CStorVolumeReplicaList{Items: []cstorv1.CStorVolumeReplica{}}}, - "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetUsedCapacityFromCVR(tt.args.cvrList); got != tt.want { - t.Errorf("GetUsedCapacityFromCVR() = %v, want %v", got, tt.want) - } - }) - } -} - func TestIsValidCasType(t *testing.T) { type args struct { casType string @@ -280,7 +62,7 @@ func TestIsValidCasType(t *testing.T) { }{ { "Valid Cas Name", - args{casType: CstorCasType}, + args{casType: LVMCasType}, true, }, { diff --git a/pkg/util/testdata_test.go b/pkg/util/testdata_test.go deleted file mode 100644 index eb758504..00000000 --- a/pkg/util/testdata_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "time" - - cstortypes "github.com/openebs/api/v2/pkg/apis/types" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var ( - fourGigiByte = resource.MustParse("4Gi") - cstorScName = "cstor-sc" - cstorVolumeMode = corev1.PersistentVolumeFilesystem - - cstorPV1 = corev1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: corev1.PersistentVolumeSpec{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - ClaimRef: &corev1.ObjectReference{ - Namespace: "default", - Name: "cstor-pvc-1", - }, - PersistentVolumeReclaimPolicy: "Retain", - StorageClassName: cstorScName, - VolumeMode: &cstorVolumeMode, - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{ - Driver: "cstor.csi.openebs.io", VolumeAttributes: map[string]string{"openebs.io/cas-type": "cstor"}, - }}, - }, - Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeBound}, - } - zfspv = corev1.PersistentVolume{ - Spec: corev1.PersistentVolumeSpec{ - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{Driver: ZFSCSIDriver}}}} - - cstorSC = v1.StorageClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-sc", - CreationTimestamp: metav1.Time{Time: time.Now()}, - }, - Provisioner: "cstor.csi.openebs.io", - } - - jivaSC = v1.StorageClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "jiva-sc", - CreationTimestamp: metav1.Time{Time: time.Now()}, - }, - Provisioner: "jiva.csi.openebs.io", - Parameters: map[string]string{"cas-type": "jiva"}, - } -) diff --git a/pkg/util/types.go b/pkg/util/types.go index 4210453b..e4464f1b 100644 --- a/pkg/util/types.go +++ b/pkg/util/types.go @@ -17,12 +17,10 @@ limitations under the License. package util import ( - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/api/v2/pkg/apis/openebs.io/v1alpha1" corev1 "k8s.io/api/core/v1" ) -//Volume struct will have all the details we want to give in the output for +// Volume struct will have all the details we want to give in the output for // openebsctl commands type Volume struct { // AccessModes contains all ways the volume can be mounted @@ -31,59 +29,34 @@ type Volume struct { AttachementStatus string // Represents the actual capacity of the underlying volume. Capacity string - // CStorPoolCluster that this volume belongs to - CSPC string - // The unique volume name returned by the CSI volume plugin to - // refer to the volume on all subsequent calls. - CSIVolumeAttachmentName string - Name string + Name string //Namespace defines the space within each name must be unique. // An empty namespace is equivalent to the "default" namespace Namespace string Node string // Name of the PVClaim of the underlying Persistent Volume PVC string - // Status of the CStor Volume - Status v1.CStorVolumePhase // Name of StorageClass to which this persistent volume belongs. StorageClass string - // will be cStorVolume for all cStor volumes - VolType string // version of the spec used to create the volumes Version string } -//VolumeInfo struct will have all the details we want to give in the output for +// VolumeInfo struct will have all the details we want to give in the output for // openebsctl command volume describe type VolumeInfo struct { AccessMode string // Capacity of the underlying PV Capacity string - // CStorPoolCluster that the volume belongs to - CSPC string - // cStor Instance Driver - CSIDriver string - CSIVolumeAttachmentName string // Name of the volume & Namespace on which it exists - Name string - Namespace string - // Name of the underlying PVC - PVC string - // ReplicationFactor represents number of volume replica created during - // volume provisioning connect to the target - ReplicaCount int + Name string + PVC string // Phase indicates if a volume is available, bound to a claim, or released // by a claim. VolumePhase corev1.PersistentVolumePhase // Name of StorageClass to which this persistent volume belongs. StorageClass string - // Version of the OpenEBS resource definition being used - Version string - Size string - // Status of the CStor volume - Status string - // JVP is the name of the JivaVolumePolicy - JVP string + Size string } type LocalHostPathVolInfo struct { @@ -93,65 +66,6 @@ type LocalHostPathVolInfo struct { CasType string } -// PortalInfo keep info about the ISCSI Target Portal. -type PortalInfo struct { - // Target iSCSI Qualified Name.combination of nodeBase - IQN string - VolumeName string - // iSCSI Target Portal. The Portal is combination of IP:port - // (typically TCP ports 3260) - Portal string - // TargetIP IP of the iSCSI target service - TargetIP string - //Node Name on which the application pod is running - TargetNodeName string -} - -// CStorReplicaInfo holds information about the cStor replicas -type CStorReplicaInfo struct { - // Replica name present on ObjectMetadata - Name string - // Node on which the replica is present - NodeName string - ID v1.ReplicaID - //Replica Status reflects the phase, i.e hold result of last action. - // ec. Healthy, Offline ,Degraded etc. - Status string -} - -// CstorPVCInfo struct will have all the details we want to give in the output for describe pvc -// details section for cstor pvc -type CstorPVCInfo struct { - Name string - Namespace string - CasType string - BoundVolume string - AttachedToNode string - Pool string - StorageClassName string - Size string - Used string - CVStatus v1.CStorVolumePhase - PVStatus corev1.PersistentVolumePhase - MountPods string -} - -// JivaPVCInfo struct will have all the details we want to give in the output for describe pvc -// details section for jiva pvc -type JivaPVCInfo struct { - Name string - Namespace string - CasType string - BoundVolume string - AttachedToNode string - JVP string - StorageClassName string - Size string - JVStatus string - PVStatus corev1.PersistentVolumePhase - MountPods string -} - // LVMPVCInfo struct will have all the details we want to give in the output for describe pvc // details section for lvm pvc type LVMPVCInfo struct { @@ -179,7 +93,7 @@ type ZFSPVCInfo struct { } // PVCInfo struct will have all the details we want to give in the output for describe pvc -// details section for non-cstor pvc +// details section for generic pvc type PVCInfo struct { Name string Namespace string @@ -191,35 +105,6 @@ type PVCInfo struct { MountPods string } -// PoolInfo struct will have all the details we want to give in the output for describe pool -// details section for cstor pool instance -type PoolInfo struct { - Name string - HostName string - Size string - FreeCapacity string - ReadOnlyStatus bool - Status v1.CStorPoolInstancePhase - RaidType string -} - -// BlockDevicesInfoInPool struct will have all the details we want to give in the output for describe pool -// details section for block devices in the cstor pool instance -type BlockDevicesInfoInPool struct { - Name string - Capacity uint64 - State v1alpha1.BlockDeviceState -} - -// CVRInfo struct will have all the details we want to give in the output for describe pool -// details section for provisional replicas in the cstor pool instance -type CVRInfo struct { - Name string - PvcName string - Size string - Status v1.CStorVolumeReplicaPhase -} - // MapOptions struct to get the resources as Map with the provided options // Key defines what to use as a key, ex:- name, label, currently these two are supported, add more according to need. // LabelKey defines which Label to use as key. @@ -245,21 +130,6 @@ const ( Label Key = "label" ) -// CstorVolumeResources would contain all the resources needed for debugging a Cstor Volume -type CstorVolumeResources struct { - PV *corev1.PersistentVolume - PVC *corev1.PersistentVolumeClaim - CV *v1.CStorVolume - CVC *v1.CStorVolumeConfig - CVA *v1.CStorVolumeAttachment - CVRs *v1.CStorVolumeReplicaList - PresentBDs *v1alpha1.BlockDeviceList - ExpectedBDs map[string]bool - BDCs *v1alpha1.BlockDeviceClaimList - CSPIs *v1.CStorPoolInstanceList - CSPC *v1.CStorPoolCluster -} - // ZFSVolDesc is the output helper for ZfsVolDesc type ZFSVolDesc struct { Name string diff --git a/pkg/util/util.go b/pkg/util/util.go index 2f9564c0..7a3aa2b4 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -32,7 +32,7 @@ import ( "k8s.io/cli-runtime/pkg/printers" "github.com/pkg/errors" - "k8s.io/klog" + "k8s.io/klog/v2" ) var Kubeconfig string @@ -65,7 +65,7 @@ func ColorText(s string, c Color) string { // Fatal prints the message (if provided) and then exits. If V(2) or greater, // klog.Fatal is invoked for extended information. func Fatal(msg string) { - if klog.V(2) { + if klog.V(2).Enabled() { klog.FatalDepth(2, msg) } if len(msg) > 0 { diff --git a/pkg/volume/cstor.go b/pkg/volume/cstor.go deleted file mode 100644 index 8e1665ec..00000000 --- a/pkg/volume/cstor.go +++ /dev/null @@ -1,254 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package volume - -import ( - "fmt" - "os" - "time" - - cstortypes "github.com/openebs/api/v2/pkg/apis/types" - "k8s.io/cli-runtime/pkg/printers" - - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - cstorVolInfoTemplate = ` -{{.Name}} Details : ------------------ -NAME : {{.Name}} -ACCESS MODE : {{.AccessMode}} -CSI DRIVER : {{.CSIDriver}} -STORAGE CLASS : {{.StorageClass}} -VOLUME PHASE : {{.VolumePhase }} -VERSION : {{.Version}} -CSPC : {{.CSPC}} -SIZE : {{.Size}} -STATUS : {{.Status}} -REPLICA COUNT : {{.ReplicaCount}} -` - cstorPortalTemplate = ` -Portal Details : ------------------- -IQN : {{.IQN}} -VOLUME NAME : {{.VolumeName}} -TARGET NODE NAME : {{.TargetNodeName}} -PORTAL : {{.Portal}} -TARGET IP : {{.TargetIP}} -` -) - -// GetCStor returns a list of CStor volumes -func GetCStor(c *client.K8sClient, pvList *corev1.PersistentVolumeList, openebsNS string) ([]metav1.TableRow, error) { - var ( - cvMap map[string]v1.CStorVolume - cvaMap map[string]v1.CStorVolumeAttachment - ) - // no need to proceed if CVs/CVAs don't exist - var err error - _, cvMap, err = c.GetCVs(nil, util.Map, "", util.MapOptions{ - Key: util.Name}) - if err != nil { - return nil, fmt.Errorf("failed to list CStorVolumes") - } - _, cvaMap, err = c.GetCVAs(util.Map, "", util.MapOptions{ - Key: util.Label, - LabelKey: "Volname"}) - if err != nil { - return nil, fmt.Errorf("failed to list CStorVolumeAttachments") - } - var rows []metav1.TableRow - // 3. Show the required ones - for _, pv := range pvList.Items { - var attachedNode, storageVersion, customStatus, ns string - // TODO: Estimate the cas-type and decide to print it out - // Should all AccessModes be shown in a csv format, or the highest be displayed ROO < RWO < RWX? - // 2. For eligible PVs fetch the custom-resource to add more info - if pv.Spec.CSI != nil && pv.Spec.CSI.Driver == util.CStorCSIDriver { - // For all CSI CStor PV, there exist a CV - cv, ok := cvMap[pv.Name] - if !ok { - // condition not possible - _, _ = fmt.Fprintf(os.Stderr, "couldn't find cv "+pv.Name) - } - ns = cv.Namespace - if openebsNS != "" && openebsNS != ns { - continue - } - customStatus = string(cv.Status.Phase) - storageVersion = cv.VersionDetails.Status.Current - cva, cvaOk := cvaMap[pv.Name] - if cvaOk { - attachedNode = cva.Labels["nodeID"] - } - // TODO: What should be done for multiple AccessModes - accessMode := pv.Spec.AccessModes[0] - rows = append(rows, metav1.TableRow{ - Cells: []interface{}{ - ns, pv.Name, customStatus, storageVersion, util.ConvertToIBytes(pv.Spec.Capacity.Storage().String()), pv.Spec.StorageClassName, pv.Status.Phase, - accessMode, attachedNode}}) - } - } - return rows, nil -} - -// DescribeCstorVolume describes a cstor storage engine PersistentVolume -func DescribeCstorVolume(c *client.K8sClient, vol *corev1.PersistentVolume) error { - // Fetch all details of a volume is called to get the volume controller's - // info such as controller's IP, status, iqn, replica IPs etc. - // 1. cStor volume info - volumeInfo, err := c.GetCV(vol.Name) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "failed to get cStorVolume for %s\n", vol.Name) - return err - } - // 2. cStor Volume Config - cvcInfo, err := c.GetCVC(vol.Name) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "failed to get cStor Volume config for %s\n", vol.Name) - return err - } - // 3. Get Node for Target Pod from the openebs-ns - node, err := c.GetCVA(util.CVAVolnameKey + "=" + vol.Name) - var nodeName string - if err != nil { - nodeName = util.NotAvailable - _, _ = fmt.Fprintf(os.Stderr, "failed to get CStorVolumeAttachments for %s\n", vol.Name) - } else { - nodeName = node.Spec.Volume.OwnerNodeID - } - - // 5. cStor Volume Replicas - cvrInfo, _ := c.GetCVRs(cstortypes.PersistentVolumeLabelKey + "=" + vol.Name) - if len(cvrInfo.Items) == 0 { - _, _ = fmt.Fprintf(os.Stderr, "failed to get cStor Volume Replicas for %s\n", vol.Name) - } - - cSPCLabel := cstortypes.CStorPoolClusterLabelKey - volume := util.VolumeInfo{ - AccessMode: util.AccessModeToString(vol.Spec.AccessModes), - Capacity: volumeInfo.Status.Capacity.String(), - CSPC: cvcInfo.Labels[cSPCLabel], - CSIDriver: vol.Spec.CSI.Driver, - CSIVolumeAttachmentName: vol.Spec.CSI.VolumeHandle, - Name: volumeInfo.Name, - Namespace: volumeInfo.Namespace, - PVC: vol.Spec.ClaimRef.Name, - ReplicaCount: volumeInfo.Spec.ReplicationFactor, - VolumePhase: vol.Status.Phase, - StorageClass: vol.Spec.StorageClassName, - Version: util.CheckVersion(volumeInfo.VersionDetails), - Size: util.ConvertToIBytes(volumeInfo.Status.Capacity.String()), - Status: string(volumeInfo.Status.Phase), - } - - // Print the output for the portal status info - _ = util.PrintByTemplate("volume", cstorVolInfoTemplate, volume) - - portalInfo := util.PortalInfo{ - IQN: volumeInfo.Spec.Iqn, - VolumeName: volumeInfo.Name, - Portal: volumeInfo.Spec.TargetPortal, - TargetIP: volumeInfo.Spec.TargetIP, - TargetNodeName: nodeName, - } - - // Print the output for the portal status info - _ = util.PrintByTemplate("PortalInfo", cstorPortalTemplate, portalInfo) - - replicaCount := volumeInfo.Spec.ReplicationFactor - // This case will occur only if user has manually specified zero replica. - // or if none of the replicas are healthy & running - if replicaCount == 0 || len(volumeInfo.Status.ReplicaStatuses) == 0 { - fmt.Printf("\nNone of the replicas are running\n") - //please check the volume pod's status by running [kubectl describe pvc -l=openebs/replica --all-namespaces]\Oor try again later.") - return nil - } - - // Print replica details - if cvrInfo != nil && len(cvrInfo.Items) > 0 { - fmt.Printf("\nReplica Details :\n-----------------\n") - var rows []metav1.TableRow - for _, cvr := range cvrInfo.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - cvr.Name, - util.ConvertToIBytes(cvr.Status.Capacity.Total), - util.ConvertToIBytes(cvr.Status.Capacity.Used), - cvr.Status.Phase, - util.Duration(time.Since(cvr.ObjectMeta.CreationTimestamp.Time))}}) - } - util.TablePrinter(util.CstorReplicaColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } - - cStorBackupList, _ := c.GetCVBackups(cstortypes.PersistentVolumeLabelKey + "=" + vol.Name) - if cStorBackupList != nil { - fmt.Printf("\nCstor Backup Details :\n" + "---------------------\n") - var rows []metav1.TableRow - for _, item := range cStorBackupList.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - item.ObjectMeta.Name, - item.Spec.BackupName, - item.Spec.VolumeName, - item.Spec.BackupDest, - item.Spec.SnapName, - item.Status, - }}) - } - util.TablePrinter(util.CstorBackupColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } - - cstorCompletedBackupList, _ := c.GetCVCompletedBackups(cstortypes.PersistentVolumeLabelKey + "=" + vol.Name) - if cstorCompletedBackupList != nil { - fmt.Printf("\nCstor Completed Backup Details :" + "\n-------------------------------\n") - var rows []metav1.TableRow - for _, item := range cstorCompletedBackupList.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - item.Name, - item.Spec.BackupName, - item.Spec.VolumeName, - item.Spec.LastSnapName, - }}) - } - util.TablePrinter(util.CstorCompletedBackupColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } - - cStorRestoreList, _ := c.GetCVRestores(cstortypes.PersistentVolumeLabelKey + "=" + vol.Name) - if cStorRestoreList != nil { - fmt.Printf("\nCstor Restores Details :" + "\n-----------------------\n") - var rows []metav1.TableRow - for _, item := range cStorRestoreList.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - item.ObjectMeta.Name, - item.Spec.RestoreName, - item.Spec.VolumeName, - item.Spec.RestoreSrc, - item.Spec.StorageClass, - item.Spec.Size.String(), - item.Status, - }}) - } - util.TablePrinter(util.CstorRestoreColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } - fmt.Println() - return nil -} diff --git a/pkg/volume/cstor_test.go b/pkg/volume/cstor_test.go deleted file mode 100644 index ff7fe226..00000000 --- a/pkg/volume/cstor_test.go +++ /dev/null @@ -1,285 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package volume - -import ( - "reflect" - "testing" - - openebsFakeClientset "github.com/openebs/api/v2/pkg/client/clientset/versioned/fake" - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" -) - -func TestDescribeCstorVolume(t *testing.T) { - cvRep := cv1 - cvRep.Spec.ReplicationFactor = 0 - cvRep.Status.ReplicaStatuses = nil - type args struct { - c *client.K8sClient - vol *corev1.PersistentVolume - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "All Valid Values", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - vol: &cstorPV1, - }, - wantErr: false, - }, - { - name: "All Valid Values with CV absent", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv2, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - vol: &cstorPV1, - }, - wantErr: true, - }, - { - name: "All Valid Values with CVC absent", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - vol: &cstorPV1, - }, - wantErr: true, - }, - { - name: "All Valid Values with CVA absent", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - vol: &cstorPV1, - }, - wantErr: false, - }, - { - name: "All Valid Values with CVRs absent", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva2, &cvc1, &cvc2, &cvr4, &cbkp, &ccbkp, &crestore), - }, - vol: &cstorPV1, - }, - wantErr: false, - }, - { - name: "All Valid Values with CVR count as 0", - args: args{ - c: &client.K8sClient{ - Ns: "cstor", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cvRep, &cv2, &cva2, &cvc1, &cvc2, &cvr4, &cbkp, &ccbkp, &crestore), - }, - vol: &cstorPV1, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DescribeCstorVolume(tt.args.c, tt.args.vol); (err != nil) != tt.wantErr { - t.Errorf("DescribeCstorVolume() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestGetCStor(t *testing.T) { - type args struct { - c *client.K8sClient - pvList *corev1.PersistentVolumeList - openebsNS string - } - tests := []struct { - name string - args args - want []metav1.TableRow - wantErr bool - }{ - { - name: "Test with all valid resources present.", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1, cstorPV2}}, - openebsNS: "cstor", - }, - want: []metav1.TableRow{{Cells: []interface{}{ - "cstor", "pvc-1", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-1"}, - }, {Cells: []interface{}{ - "cstor", "pvc-2", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-2"}, - }}, - wantErr: false, - }, - { - name: "Test with one of the required cv not present", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cva1, &cva2, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1, cstorPV2}}, - openebsNS: "cstor", - }, - want: []metav1.TableRow{{Cells: []interface{}{ - "cstor", "pvc-1", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-1"}, - }}, - wantErr: false, - }, - { - name: "Test with one of the required cva not present, i.e node cannot be determined", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cvc1, &cvc2, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1, cstorPV2}}, - openebsNS: "cstor", - }, - want: []metav1.TableRow{{Cells: []interface{}{ - "cstor", "pvc-1", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-1"}, - }, {Cells: []interface{}{ - "cstor", "pvc-2", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, ""}, - }}, - wantErr: false, - }, - { - name: "Test with one of the required cvc not present, i.e nothing should break in code", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvr1, &cvr2, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1, cstorPV2}}, - openebsNS: "cstor", - }, - want: []metav1.TableRow{{Cells: []interface{}{ - "cstor", "pvc-1", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-1"}, - }, {Cells: []interface{}{ - "cstor", "pvc-2", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-2"}, - }}, - wantErr: false, - }, - { - name: "Test with two of the required cvrs not present, i.e nothing should break in code", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvr3, &cvr4, &cbkp, &ccbkp, &crestore), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1, cstorPV2}}, - openebsNS: "cstor", - }, - want: []metav1.TableRow{{Cells: []interface{}{ - "cstor", "pvc-1", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-1"}, - }, {Cells: []interface{}{ - "cstor", "pvc-2", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-2"}, - }}, - wantErr: false, - }, - { - name: "Test with backup and restore crs not present, i.e nothing should break in code", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1, &cv2, &cva1, &cva2, &cvc1, &cvr3, &cvr4), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1, cstorPV2}}, - openebsNS: "cstor", - }, - want: []metav1.TableRow{{Cells: []interface{}{ - "cstor", "pvc-1", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-1"}, - }, {Cells: []interface{}{ - "cstor", "pvc-2", util.Healthy, "2.11.0", "4.0GiB", "cstor-sc", corev1.VolumeBound, corev1.ReadWriteOnce, "node-2"}, - }}, - wantErr: false, - }, - { - name: "Test with none of the underlying cstor crs", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1}}, - openebsNS: "cstor", - }, - want: nil, - wantErr: true, - }, - { - name: "Test with none of the underlying cvas are present", - args: args{ - c: &client.K8sClient{ - Ns: "", - K8sCS: fake.NewSimpleClientset(&cstorPV1, &cstorPV2, &cstorPVC1, &cstorPVC2, &nsCstor), - OpenebsCS: openebsFakeClientset.NewSimpleClientset(&cv1), - }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{cstorPV1}}, - openebsNS: "cstor", - }, - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetCStor(tt.args.c, tt.args.pvList, tt.args.openebsNS) - if (err != nil) != tt.wantErr { - t.Errorf("GetCStor() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetCStor() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/volume/jiva.go b/pkg/volume/jiva.go deleted file mode 100644 index abf905d5..00000000 --- a/pkg/volume/jiva.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package volume - -import ( - "fmt" - "os" - "strings" - "time" - - "k8s.io/cli-runtime/pkg/printers" - - "github.com/openebs/openebsctl/pkg/client" - "github.com/openebs/openebsctl/pkg/util" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - // JivaVolInfoTemplate to store the jiva volume and pvc describe related details - JivaVolInfoTemplate = ` -{{.Name}} Details : ------------------ -NAME : {{.Name}} -ACCESS MODE : {{.AccessMode}} -CSI DRIVER : {{.CSIDriver}} -STORAGE CLASS : {{.StorageClass}} -VOLUME PHASE : {{.VolumePhase }} -VERSION : {{.Version}} -JVP : {{.JVP}} -SIZE : {{.Size}} -STATUS : {{.Status}} -REPLICA COUNT : {{.ReplicaCount}} - -` - // JivaPortalTemplate to store the portal details for jiva volume and pvc - JivaPortalTemplate = ` -Portal Details : ------------------- -IQN : {{.spec.iscsiSpec.iqn}} -VOLUME NAME : {{.metadata.name}} -TARGET NODE NAME : {{.metadata.labels.nodeID}} -PORTAL : {{.spec.iscsiSpec.targetIP}}:{{.spec.iscsiSpec.targetPort}} - -` -) - -// GetJiva returns a list of JivaVolumes -func GetJiva(c *client.K8sClient, pvList *corev1.PersistentVolumeList, openebsNS string) ([]metav1.TableRow, error) { - // 1. Fetch all relevant volume CRs without worrying about openebsNS - _, jvMap, err := c.GetJVs(nil, util.Map, "", util.MapOptions{Key: util.Name}) - if err != nil { - return nil, fmt.Errorf("failed to list JivaVolumes") - } - var rows []metav1.TableRow - // 3. Show the required ones - for _, pv := range pvList.Items { - name := pv.Name - capacity := pv.Spec.Capacity.Storage() - sc := pv.Spec.StorageClassName - attached := pv.Status.Phase - var attachedNode, storageVersion, customStatus, ns string - // Should all AccessModes be shown in a csv format, or the highest be displayed ROO < RWO < RWX? - if pv.Spec.CSI != nil && pv.Spec.CSI.Driver == util.JivaCSIDriver { - jv, ok := jvMap[pv.Name] - if !ok { - _, _ = fmt.Fprintln(os.Stderr, "couldn't find jv "+pv.Name) - } - ns = jv.Namespace - if openebsNS != "" && openebsNS != ns { - continue - } - customStatus = jv.Status.Status // RW, RO, etc - attachedNode = jv.Labels["nodeID"] - storageVersion = jv.VersionDetails.Status.Current - } else { - // Skip non-Jiva options - continue - } - accessMode := pv.Spec.AccessModes[0] - rows = append(rows, metav1.TableRow{ - Cells: []interface{}{ - ns, name, customStatus, storageVersion, capacity, sc, attached, - accessMode, attachedNode}, - }) - } - return rows, nil -} - -// DescribeJivaVolume describes a jiva storage engine PersistentVolume -func DescribeJivaVolume(c *client.K8sClient, vol *corev1.PersistentVolume) error { - // 1. Get the JivaVolume Corresponding to the pv name - jv, err := c.GetJV(vol.Name) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "failed to get JivaVolume for %s\n", vol.Name) - return err - } - // 2. Fill in JivaVolume related details - jivaVolInfo := util.VolumeInfo{ - AccessMode: util.AccessModeToString(vol.Spec.AccessModes), - Capacity: util.ConvertToIBytes(vol.Spec.Capacity.Storage().String()), - CSIDriver: vol.Spec.CSI.Driver, - Name: jv.Name, - Namespace: jv.Namespace, - PVC: vol.Spec.ClaimRef.Name, - ReplicaCount: jv.Spec.Policy.Target.ReplicationFactor, - VolumePhase: vol.Status.Phase, - StorageClass: vol.Spec.StorageClassName, - Version: jv.VersionDetails.Status.Current, - Size: util.ConvertToIBytes(vol.Spec.Capacity.Storage().String()), - Status: jv.Status.Status, - JVP: jv.Annotations["openebs.io/volume-policy"], - } - // 3. Print the Volume information - _ = util.PrintByTemplate("jivaVolumeInfo", JivaVolInfoTemplate, jivaVolInfo) - // 4. Print the Portal Information - util.TemplatePrinter(JivaPortalTemplate, jv) - - replicaPodIPAndModeMap := make(map[string]string) - // Create Replica IP to Mode Map - if jv.Status.ReplicaStatuses != nil && len(jv.Status.ReplicaStatuses) != 0 { - for _, replicaStatus := range jv.Status.ReplicaStatuses { - replicaPodIPAndModeMap[strings.Split(replicaStatus.Address, ":")[1][2:]] = replicaStatus.Mode - } - } - - // 5. Fetch the Jiva controller and replica pod details - podList, err := c.GetJVTargetPod(vol.Name) - if err == nil { - fmt.Println("Controller and Replica Pod Details :") - fmt.Println("-----------------------------------") - var rows []metav1.TableRow - for _, pod := range podList.Items { - if strings.Contains(pod.Name, "-ctrl-") { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - pod.Namespace, pod.Name, jv.Status.Status, - pod.Spec.NodeName, pod.Status.Phase, pod.Status.PodIP, - util.GetReadyContainers(pod.Status.ContainerStatuses), - util.Duration(time.Since(pod.ObjectMeta.CreationTimestamp.Time))}}) - } else { - if val, ok := replicaPodIPAndModeMap[pod.Status.PodIP]; ok { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - pod.Namespace, pod.Name, val, - pod.Spec.NodeName, pod.Status.Phase, pod.Status.PodIP, - util.GetReadyContainers(pod.Status.ContainerStatuses), - util.Duration(time.Since(pod.ObjectMeta.CreationTimestamp.Time))}}) - } - } - } - util.TablePrinter(util.JivaPodDetailsColumnDefinations, rows, printers.PrintOptions{Wide: true}) - } else { - fmt.Println("Controller and Replica Pod Details :") - fmt.Println("-----------------------------------") - fmt.Println("No Controller and Replica pod exists for the JivaVolume") - } - // 6. Fetch the replica PVCs and create rows for cli-runtime - var rows []metav1.TableRow - pvcList, err := c.GetPVCs(c.Ns, nil, "openebs.io/component=jiva-replica,openebs.io/persistent-volume="+jv.Name) - if err != nil || len(pvcList.Items) == 0 { - fmt.Printf("No replicas found for the JivaVolume %s", vol.Name) - return nil - } - for _, pvc := range pvcList.Items { - rows = append(rows, metav1.TableRow{Cells: []interface{}{ - pvc.Name, - pvc.Status.Phase, - pvc.Spec.VolumeName, - util.ConvertToIBytes(pvc.Spec.Resources.Requests.Storage().String()), - *pvc.Spec.StorageClassName, - util.Duration(time.Since(pvc.ObjectMeta.CreationTimestamp.Time)), - pvc.Spec.VolumeMode}}) - } - // 6. Print the replica details if present - fmt.Println() - fmt.Println("Replica Data Volume Details :") - fmt.Println("-----------------------------") - util.TablePrinter(util.JivaReplicaPVCColumnDefinations, rows, printers.PrintOptions{Wide: true}) - return nil -} diff --git a/pkg/volume/jiva_test.go b/pkg/volume/jiva_test.go deleted file mode 100644 index 48631eff..00000000 --- a/pkg/volume/jiva_test.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2020-2022 The OpenEBS Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package volume - -import ( - "reflect" - "testing" - - "github.com/openebs/openebsctl/pkg/client" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestDescribeJivaVolume(t *testing.T) { - type args struct { - c *client.K8sClient - vol *corev1.PersistentVolume - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := DescribeJivaVolume(tt.args.c, tt.args.vol); (err != nil) != tt.wantErr { - t.Errorf("DescribeJivaVolume() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestGetJiva(t *testing.T) { - type args struct { - c *client.K8sClient - pvList *corev1.PersistentVolumeList - openebsNS string - } - tests := []struct { - name string - args args - want []metav1.TableRow - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetJiva(tt.args.c, tt.args.pvList, tt.args.openebsNS) - if (err != nil) != tt.wantErr { - t.Errorf("GetJiva() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetJiva() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/volume/local_hostpath_test.go b/pkg/volume/local_hostpath_test.go index 075eedec..5e7cee2d 100644 --- a/pkg/volume/local_hostpath_test.go +++ b/pkg/volume/local_hostpath_test.go @@ -42,11 +42,10 @@ func TestGetLocalHostpath(t *testing.T) { name: "no local hostpath volumes present", args: args{ c: &client.K8sClient{ - Ns: "random-namespace", - K8sCS: k8sfake.NewSimpleClientset(), - OpenebsCS: nil, + Ns: "random-namespace", + K8sCS: k8sfake.NewSimpleClientset(), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, pv2}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{zfsPV1, lvmPV1}}, openebsNS: "openebs", }, want: []metav1.TableRow{}, @@ -59,7 +58,7 @@ func TestGetLocalHostpath(t *testing.T) { Ns: "lvmlocalpv", K8sCS: k8sfake.NewSimpleClientset(&localpvHostpathDpl1), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, localHostpathPv1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{zfsPV1, localHostpathPv1}}, openebsNS: "localhostpath", }, wantErr: false, @@ -76,7 +75,7 @@ func TestGetLocalHostpath(t *testing.T) { Ns: "lvmlocalpv", K8sCS: k8sfake.NewSimpleClientset(&localpvHostpathDpl1, &localpvHostpathDpl2), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, localHostpathPv1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{zfsPV1, localHostpathPv1}}, openebsNS: "localhostpath", }, wantErr: false, diff --git a/pkg/volume/lvmlocalpv_test.go b/pkg/volume/lvmlocalpv_test.go index 69c96155..daaa7e7c 100644 --- a/pkg/volume/lvmlocalpv_test.go +++ b/pkg/volume/lvmlocalpv_test.go @@ -47,12 +47,11 @@ func TestGetLVMLocalPV(t *testing.T) { name: "no lvm volumes present", args: args{ c: &client.K8sClient{ - Ns: "random-namespace", - LVMCS: fake.NewSimpleClientset(), - K8sCS: k8sfake.NewSimpleClientset(), - OpenebsCS: nil, + Ns: "random-namespace", + LVMCS: fake.NewSimpleClientset(), + K8sCS: k8sfake.NewSimpleClientset(), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, pv2, pv3}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{zfsPV1, localHostpathPv1}}, lvmReactors: lvmVolNotExists, openebsNS: "openebs", }, @@ -67,7 +66,7 @@ func TestGetLVMLocalPV(t *testing.T) { K8sCS: k8sfake.NewSimpleClientset(&localpvCSICtrlSTS), LVMCS: fake.NewSimpleClientset(&lvmVol1), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, lvmPV1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{zfsPV1, lvmPV1}}, openebsNS: "lvmlocalpv", }, wantErr: false, @@ -95,11 +94,11 @@ func TestGetLVMLocalPV(t *testing.T) { name: "only one lvm volume present, namespace conflicts", args: args{ c: &client.K8sClient{ - Ns: "jiva", + Ns: "zfs", K8sCS: k8sfake.NewSimpleClientset(&localpvCSICtrlSTS), LVMCS: fake.NewSimpleClientset(&lvmVol1), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, lvmPV1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{zfsPV1, lvmPV1}}, openebsNS: "lvmlocalpvXYZ", }, wantErr: false, @@ -174,7 +173,7 @@ func TestDescribeLVMLocalPVs(t *testing.T) { { "one lvm volume present and some other volume asked for", args{c: &client.K8sClient{Ns: "lvm", K8sCS: k8sfake.NewSimpleClientset(&lvmPV1), LVMCS: fake.NewSimpleClientset(&lvmVol1)}, - vol: &cstorPV2, + vol: &zfsPV1, lvmfunc: lvmVolNotExists}, false, }, diff --git a/pkg/volume/testdata_test.go b/pkg/volume/testdata_test.go index 509c2271..fee26f76 100644 --- a/pkg/volume/testdata_test.go +++ b/pkg/volume/testdata_test.go @@ -19,8 +19,6 @@ package volume import ( "time" - v1 "github.com/openebs/api/v2/pkg/apis/cstor/v1" - cstortypes "github.com/openebs/api/v2/pkg/apis/types" lvm "github.com/openebs/lvm-localpv/pkg/apis/openebs.io/lvm/v1alpha1" "github.com/openebs/openebsctl/pkg/util" zfs "github.com/openebs/zfs-localpv/pkg/apis/openebs.io/zfs/v1" @@ -36,378 +34,6 @@ var ( blockFS = corev1.PersistentVolumeBlock ) -/**************** -* CSTOR -****************/ - -var nsCstor = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - }, - Spec: corev1.NamespaceSpec{Finalizers: []corev1.FinalizerName{corev1.FinalizerKubernetes}}, -} - -var cv1 = v1.CStorVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeSpec{ - Capacity: fourGigiByte, - TargetIP: "10.2.2.2", - TargetPort: "3002", - Iqn: "pvc1-some-fake-iqn", - TargetPortal: "10.2.2.2:3002", - ReplicationFactor: 3, - ConsistencyFactor: 0, - DesiredReplicationFactor: 0, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-1-rep-1", "some-id-2": "pvc-1-rep-2", "some-id-3": "pvc-1-rep-3"}, - }, - }, - Status: v1.CStorVolumeStatus{ - Phase: util.Healthy, - ReplicaStatuses: []v1.ReplicaStatus{{ID: "some-id-1", Mode: "Healthy"}, {ID: "some-id-2", Mode: "Healthy"}, {ID: "some-id-3", Mode: "Healthy"}}, - Capacity: fourGigiByte, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-1-rep-1", "some-id-2": "pvc-1-rep-2", "some-id-3": "pvc-1-rep-3"}, - }, - }, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{ - DependentsUpgraded: true, - Current: "2.11.0", - LastUpdateTime: metav1.Time{}, - }, - }, -} - -var cv2 = v1.CStorVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeSpec{ - Capacity: fourGigiByte, - TargetIP: "10.2.2.2", - TargetPort: "3002", - Iqn: "pvc1-some-fake-iqn", - TargetPortal: "10.2.2.2:3002", - ReplicationFactor: 3, - ConsistencyFactor: 0, - DesiredReplicationFactor: 0, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-2-rep-1"}, - }, - }, - Status: v1.CStorVolumeStatus{ - Phase: util.Healthy, - ReplicaStatuses: []v1.ReplicaStatus{{ID: "some-id-1", Mode: "Healthy"}}, - Capacity: fourGigiByte, - ReplicaDetails: v1.CStorVolumeReplicaDetails{KnownReplicas: map[v1.ReplicaID]string{ - "some-id-1": "pvc-2-rep-1"}, - }, - }, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{ - DependentsUpgraded: true, - Current: "2.11.0", - LastUpdateTime: metav1.Time{}, - }, - }, -} - -var cvc1 = v1.CStorVolumeConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeConfigSpec{Provision: v1.VolumeProvision{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - ReplicaCount: 3, - }}, - Publish: v1.CStorVolumeConfigPublish{}, - Status: v1.CStorVolumeConfigStatus{PoolInfo: []string{"pool-1", "pool-2", "pool-3"}}, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{Current: "2.11.0"}, - }, -} - -var cvc2 = v1.CStorVolumeConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeConfigSpec{Provision: v1.VolumeProvision{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - ReplicaCount: 3, - }}, - Publish: v1.CStorVolumeConfigPublish{}, - Status: v1.CStorVolumeConfigStatus{PoolInfo: []string{"pool-1"}}, - VersionDetails: v1.VersionDetails{ - AutoUpgrade: false, - Desired: "2.11.0", - Status: v1.VersionStatus{Current: "2.11.0"}, - }, -} - -var cva1 = v1.CStorVolumeAttachment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-cva", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"Volname": "pvc-1", "nodeID": "node-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeAttachmentSpec{Volume: v1.VolumeInfo{OwnerNodeID: "node-1"}}, -} - -var cva2 = v1.CStorVolumeAttachment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2-cva", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{"Volname": "pvc-2", "nodeID": "node-2"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Spec: v1.CStorVolumeAttachmentSpec{Volume: v1.VolumeInfo{OwnerNodeID: "node-2"}}, -} - -var cvr1 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr2 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr3 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1-rep-3", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var cvr4 = v1.CStorVolumeReplica{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2-rep-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - Namespace: "cstor", - }, - Status: v1.CStorVolumeReplicaStatus{ - Capacity: v1.CStorVolumeReplicaCapacityDetails{ - Total: "4Gi", - Used: "70Mi", - }, - Phase: v1.CVRStatusOnline, - }, -} - -var ( - cstorScName = "cstor-sc" - cstorVolumeMode = corev1.PersistentVolumeFilesystem - cstorPVC1 = corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - Namespace: "default", - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - Resources: corev1.ResourceRequirements{Requests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceStorage: fourGigiByte}}, - VolumeName: "pvc-1", - StorageClassName: &cstorScName, - VolumeMode: &cstorVolumeMode, - }, - Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimBound, Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}}, - } -) - -var ( - cstorPVC2 = corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cstor-pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - Namespace: "default", - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - Resources: corev1.ResourceRequirements{Requests: map[corev1.ResourceName]resource.Quantity{corev1.ResourceStorage: fourGigiByte}}, - VolumeName: "pvc-2", - StorageClassName: &cstorScName, - VolumeMode: &cstorVolumeMode, - }, - Status: corev1.PersistentVolumeClaimStatus{Phase: corev1.ClaimBound, Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}}, - } -) - -var ( - cstorPV1 = corev1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: corev1.PersistentVolumeSpec{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - ClaimRef: &corev1.ObjectReference{ - Namespace: "default", - Name: "cstor-pvc-1", - }, - PersistentVolumeReclaimPolicy: "Retain", - StorageClassName: cstorScName, - VolumeMode: &cstorVolumeMode, - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{ - Driver: "cstor.csi.openebs.io", - }}, - }, - Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeBound}, - } -) - -var ( - cstorPV2 = corev1.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-2", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-2"}, - Finalizers: []string{}, - }, - Spec: corev1.PersistentVolumeSpec{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - AccessModes: []corev1.PersistentVolumeAccessMode{"ReadWriteOnce"}, - ClaimRef: &corev1.ObjectReference{ - Namespace: "default", - Name: "cstor-pvc-2", - }, - PersistentVolumeReclaimPolicy: "Retain", - StorageClassName: cstorScName, - VolumeMode: &cstorVolumeMode, - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{ - Driver: "cstor.csi.openebs.io", - }}, - }, - Status: corev1.PersistentVolumeStatus{Phase: corev1.VolumeBound}, - } -) - -var cbkp = v1.CStorBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bkp-name", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: v1.CStorBackupSpec{ - BackupName: "bkp-name", - VolumeName: "pvc-1", - SnapName: "snap-name", - PrevSnapName: "prev-snap-name", - BackupDest: "10.2.2.7", - LocalSnap: true, - }, - Status: v1.BKPCStorStatusDone, -} - -var ccbkp = v1.CStorCompletedBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "completed-bkp-name", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: v1.CStorCompletedBackupSpec{ - BackupName: "completed-bkp-name", - VolumeName: "pvc-1", - SecondLastSnapName: "secondlast-snapshot-name", - LastSnapName: "last-snapshot-name", - }, -} - -var crestore = v1.CStorRestore{ - ObjectMeta: metav1.ObjectMeta{ - Name: "restore-name", - CreationTimestamp: metav1.Time{Time: time.Now()}, - Labels: map[string]string{cstortypes.PersistentVolumeLabelKey: "pvc-1"}, - Finalizers: []string{}, - }, - Spec: v1.CStorRestoreSpec{ - RestoreName: "restore-name", - VolumeName: "pvc-1", - RestoreSrc: "10.2.2.7", - MaxRetryCount: 3, - RetryCount: 2, - StorageClass: "cstor-sc", - Size: fourGigiByte, - Local: true, - }, -} - /**************** * LVM LOCAL PV ****************/ @@ -571,175 +197,6 @@ var localpvzfsCSICtrlSTS = appsv1.StatefulSet{ }, } -/**************** -* JIVA -****************/ - -// var nsJiva = corev1.Namespace{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "jiva", -// CreationTimestamp: metav1.Time{Time: time.Now()}, -// Labels: map[string]string{}, -// Finalizers: []string{}, -// }, -// Spec: corev1.NamespaceSpec{Finalizers: []corev1.FinalizerName{corev1.FinalizerKubernetes}}, -// } - -// pvc-1 JivaVolume from jiva namespace attached on worker-node-1 & 1-replica & 2.10.0 -// var jv1 = v1alpha1.JivaVolume{ -// TypeMeta: metav1.TypeMeta{}, -// ObjectMeta: metav1.ObjectMeta{ -// Name: "pvc-1", -// Namespace: "jiva", -// Labels: map[string]string{"nodeID": "worker-node-1"}, -// }, -// Spec: v1alpha1.JivaVolumeSpec{}, -// Status: v1alpha1.JivaVolumeStatus{ -// Status: "RW", -// ReplicaCount: 1, -// ReplicaStatuses: nil, // TODO -// Phase: "Attached", -// }, -// VersionDetails: v1alpha1.VersionDetails{ -// AutoUpgrade: false, -// Desired: "2.10.0", -// Status: v1alpha1.VersionStatus{ -// DependentsUpgraded: false, -// Current: "2.10.0", -// }, -// }, -// } -//// pvc-2 JivaVolume from jiva namespace attached on worker-node-2, two replicas & 2.10.0 -// var jv2 = v1alpha1.JivaVolume{ -// TypeMeta: metav1.TypeMeta{}, -// ObjectMeta: metav1.ObjectMeta{ -// Name: "pvc-2", -// Namespace: "jiva", -// Labels: map[string]string{"nodeID": "worker-node-2"}, -// }, -// Spec: v1alpha1.JivaVolumeSpec{ -// PV: "pvc-2", -// Capacity: "4Gi", -// AccessType: "", -// ISCSISpec: v1alpha1.ISCSISpec{ -// TargetIP: "1.2.3.4", -// TargetPort: 8080, -// Iqn: "nice-iqn", -// }, -// MountInfo: v1alpha1.MountInfo{ -// StagingPath: "/home/staging/", -// TargetPath: "/home/target", -// FSType: "ext4", -// DevicePath: "", -// }, -// Policy: v1alpha1.JivaVolumePolicySpec{}, -// DesiredReplicationFactor: 0, -// }, -// Status: v1alpha1.JivaVolumeStatus{ -// Status: "RO", -// ReplicaCount: 2, -// ReplicaStatuses: []v1alpha1.ReplicaStatus{ -// {Address: "tcp://192.168.2.7:9502", Mode: "RW"}, -// {Address: "tcp://192.168.2.8:9502", Mode: "RO"}, -// }, -// Phase: "Ready", -// }, -// VersionDetails: v1alpha1.VersionDetails{ -// AutoUpgrade: false, -// Desired: "2.10.0", -// Status: v1alpha1.VersionStatus{ -// DependentsUpgraded: false, -// Current: "2.10.0", -// }, -// }, -// } -var jivaPV1 = corev1.PersistentVolume{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - Namespace: "jiva", - Labels: map[string]string{}, - Annotations: map[string]string{}, - }, - Spec: corev1.PersistentVolumeSpec{ - // 4GiB - Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, - PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{Driver: util.JivaCSIDriver}}, - AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, - ClaimRef: &corev1.ObjectReference{ - Kind: "PersistentVolumeClaim", - Namespace: "jiva-app", - Name: "mongo-jiva", - APIVersion: "v1", - ResourceVersion: "123"}, - PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimDelete, - StorageClassName: "pvc-1-sc", - VolumeMode: &blockFS, - NodeAffinity: &corev1.VolumeNodeAffinity{ - Required: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{ - {MatchExpressions: []corev1.NodeSelectorRequirement{ - {Key: "kubernetes.io/hostname", Operator: corev1.NodeSelectorOpIn, Values: []string{"node1"}}, - }}, - }}, - }, - }, - Status: corev1.PersistentVolumeStatus{ - Phase: corev1.VolumeBound, - Message: "", - Reason: "", - }, -} - -//var jivaPV2 = corev1.PersistentVolume{ -// TypeMeta: metav1.TypeMeta{}, -// ObjectMeta: metav1.ObjectMeta{ -// Name: "pvc-2", -// Namespace: "jiva", -// Labels: map[string]string{}, -// Annotations: map[string]string{}, -// }, -// Spec: corev1.PersistentVolumeSpec{ -// // 4GiB -// Capacity: corev1.ResourceList{corev1.ResourceStorage: fourGigiByte}, -// PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{Driver: util.JivaCSIDriver}}, -// AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, -// ClaimRef: nil, -// PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimDelete, -// StorageClassName: "pvc-2-sc", -// VolumeMode: &blockFS, -// NodeAffinity: &corev1.VolumeNodeAffinity{ -// Required: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{ -// {MatchExpressions: []corev1.NodeSelectorRequirement{ -// {Key: "kubernetes.io/hostname", Operator: corev1.NodeSelectorOpIn, Values: []string{"node2"}}, -// }}, -// }}, -// }, -// }, -// Status: corev1.PersistentVolumeStatus{ -// Phase: corev1.VolumePending, -// Message: "Storage class not found", -// Reason: "K8s API was down", -// }, -//} -var pv2 = corev1.PersistentVolume{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - }, - Spec: corev1.PersistentVolumeSpec{ - Capacity: corev1.ResourceList{corev1.ResourceStorage: resource.Quantity{}}, - }, - Status: corev1.PersistentVolumeStatus{}, -} -var pv3 = corev1.PersistentVolume{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Name: "pvc-1", - }, - Spec: corev1.PersistentVolumeSpec{}, - Status: corev1.PersistentVolumeStatus{}, -} - /**************** * Local Hostpath ****************/ diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 02c0cb75..4d2a6372 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -122,15 +122,13 @@ func Describe(vols []string, openebsNs string) error { // CasList returns a list of functions by cas-types for volume listing func CasList() []func(*client.K8sClient, *corev1.PersistentVolumeList, string) ([]metav1.TableRow, error) { // a good hack to implement immutable lists in Golang & also write tests for it - return []func(*client.K8sClient, *corev1.PersistentVolumeList, string) ([]metav1.TableRow, error){GetJiva, GetCStor, GetZFSLocalPVs, GetLVMLocalPV, GetLocalHostpath} + return []func(*client.K8sClient, *corev1.PersistentVolumeList, string) ([]metav1.TableRow, error){GetZFSLocalPVs, GetLVMLocalPV, GetLocalHostpath} } // CasListMap returns a map cas-types to functions for volume listing func CasListMap() map[string]func(*client.K8sClient, *corev1.PersistentVolumeList, string) ([]metav1.TableRow, error) { // a good hack to implement immutable maps in Golang & also write tests for it return map[string]func(*client.K8sClient, *corev1.PersistentVolumeList, string) ([]metav1.TableRow, error){ - util.JivaCasType: GetJiva, - util.CstorCasType: GetCStor, util.ZFSCasType: GetZFSLocalPVs, util.LVMCasType: GetLVMLocalPV, util.LocalPvHostpathCasType: GetLocalHostpath, @@ -141,8 +139,6 @@ func CasListMap() map[string]func(*client.K8sClient, *corev1.PersistentVolumeLis func CasDescribeMap() map[string]func(*client.K8sClient, *corev1.PersistentVolume) error { // a good hack to implement immutable maps in Golang & also write tests for it return map[string]func(*client.K8sClient, *corev1.PersistentVolume) error{ - util.JivaCasType: DescribeJivaVolume, - util.CstorCasType: DescribeCstorVolume, util.ZFSCasType: DescribeZFSLocalPVs, util.LVMCasType: DescribeLVMLocalPVs, util.LocalPvHostpathCasType: DescribeLocalHostpathVolume, diff --git a/pkg/volume/volume_test.go b/pkg/volume/volume_test.go index f81c9973..d07da460 100644 --- a/pkg/volume/volume_test.go +++ b/pkg/volume/volume_test.go @@ -25,7 +25,7 @@ import ( "testing" ) -const supportedCasTypeCount = 5 +const supportedCasTypeCount = 3 // TestCasList is a dummy test which ensures that each cas-type volumes can be // listed individually as well as collectively diff --git a/pkg/volume/zfs_localpv_test.go b/pkg/volume/zfs_localpv_test.go index fdfe6a25..6e32ed8f 100644 --- a/pkg/volume/zfs_localpv_test.go +++ b/pkg/volume/zfs_localpv_test.go @@ -49,12 +49,11 @@ func TestGetZFSLocalPVs(t *testing.T) { name: "no zfs volumes present", args: args{ c: &client.K8sClient{ - Ns: "random-namespace", - ZFCS: fake.NewSimpleClientset(), - K8sCS: k8sfake.NewSimpleClientset(), - OpenebsCS: nil, + Ns: "random-namespace", + ZFCS: fake.NewSimpleClientset(), + K8sCS: k8sfake.NewSimpleClientset(), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, pv2, pv3}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{lvmPV1, localHostpathPv1}}, zfsReactors: zfsVolNotExists, openebsNS: "zfslocalpv", }, @@ -69,7 +68,7 @@ func TestGetZFSLocalPVs(t *testing.T) { K8sCS: k8sfake.NewSimpleClientset(&localpvzfsCSICtrlSTS), ZFCS: fake.NewSimpleClientset(&zfsVol1), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, zfsPV1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{lvmPV1, zfsPV1}}, openebsNS: "zfslocalpv", }, wantErr: false, @@ -97,11 +96,11 @@ func TestGetZFSLocalPVs(t *testing.T) { name: "only one zfs volume present, namespace conflicts", args: args{ c: &client.K8sClient{ - Ns: "jiva", + Ns: "lvm", K8sCS: k8sfake.NewSimpleClientset(&localpvzfsCSICtrlSTS), ZFCS: fake.NewSimpleClientset(&zfsVol1), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, zfsPV1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{lvmPV1, zfsPV1}}, openebsNS: "zfslocalpvXYZ", }, wantErr: false, @@ -111,11 +110,11 @@ func TestGetZFSLocalPVs(t *testing.T) { name: "controller sts not present", args: args{ c: &client.K8sClient{ - Ns: "jiva", + Ns: "lvm", K8sCS: k8sfake.NewSimpleClientset(), ZFCS: fake.NewSimpleClientset(&zfsVol1), }, - pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{jivaPV1, zfsPV1}}, + pvList: &corev1.PersistentVolumeList{Items: []corev1.PersistentVolume{lvmPV1, zfsPV1}}, openebsNS: "zfslocalpv", }, wantErr: false, @@ -181,7 +180,7 @@ func TestDescribeZFSLocalPVs(t *testing.T) { }, {"one zfs volume present and some other volume asked for", args{c: &client.K8sClient{Ns: "zfs", K8sCS: k8sfake.NewSimpleClientset(&zfsPV1), ZFCS: fake.NewSimpleClientset(&zfsVol1)}, - vol: &cstorPV2, + vol: &lvmPV1, zfsfunc: zfsVolNotExists}, false, },