Drop vsphere cloud provider
Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
206
LICENSES/vendor/github.com/vmware/govmomi/LICENSE
generated
vendored
206
LICENSES/vendor/github.com/vmware/govmomi/LICENSE
generated
vendored
@@ -1,206 +0,0 @@
|
|||||||
= vendor/github.com/vmware/govmomi licensed under: =
|
|
||||||
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
= vendor/github.com/vmware/govmomi/LICENSE.txt 3b83ef96387f14655fc854ddc3c6bd57
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
apiVersion: storage.k8s.io/v1
|
|
||||||
kind: StorageClass
|
|
||||||
metadata:
|
|
||||||
name: thin
|
|
||||||
annotations:
|
|
||||||
storageclass.kubernetes.io/is-default-class: "true"
|
|
||||||
labels:
|
|
||||||
provisioner: kubernetes.io/vsphere-volume
|
|
||||||
parameters:
|
|
||||||
diskformat: thin
|
|
||||||
@@ -26,5 +26,4 @@ import (
|
|||||||
// NOTE: Importing all in-tree cloud-providers is not required when
|
// NOTE: Importing all in-tree cloud-providers is not required when
|
||||||
// implementing an out-of-tree cloud-provider.
|
// implementing an out-of-tree cloud-provider.
|
||||||
_ "k8s.io/legacy-cloud-providers/gce"
|
_ "k8s.io/legacy-cloud-providers/gce"
|
||||||
_ "k8s.io/legacy-cloud-providers/vsphere"
|
|
||||||
)
|
)
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -62,7 +62,6 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
github.com/vmware/govmomi v0.30.6
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.10
|
go.etcd.io/etcd/api/v3 v3.5.10
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.10
|
go.etcd.io/etcd/client/pkg/v3 v3.5.10
|
||||||
go.etcd.io/etcd/client/v3 v3.5.10
|
go.etcd.io/etcd/client/v3 v3.5.10
|
||||||
@@ -87,7 +86,6 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d
|
||||||
google.golang.org/grpc v1.58.3
|
google.golang.org/grpc v1.58.3
|
||||||
google.golang.org/protobuf v1.31.0
|
google.golang.org/protobuf v1.31.0
|
||||||
gopkg.in/gcfg.v1 v1.2.3
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@@ -225,6 +223,7 @@ require (
|
|||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
|
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -184,7 +184,6 @@ github.com/Microsoft/hcsshim v0.8.25/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01
|
|||||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
|
|
||||||
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/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-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
@@ -304,7 +303,6 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
|
|||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02/go.mod h1:7NQ3kWOx2cZOSjtcveTa5nqupVr2s6/83sG+rTlI7uA=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
@@ -673,7 +671,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rasky/go-xdr v0.0.0-20170217172119-4930550ba2e2/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
@@ -746,9 +743,6 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp
|
|||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/vmware/govmomi v0.30.6 h1:O3tjSwQBy0XwI5uK1/yVIfQ1LP9bAECEDUfifnyGs9U=
|
|
||||||
github.com/vmware/govmomi v0.30.6/go.mod h1:epgoslm97rLECMV4D+08ORzUBEU7boFSepKjt7AYVGg=
|
|
||||||
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
|
|
||||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
|||||||
@@ -22,5 +22,4 @@ package cloudprovider
|
|||||||
import (
|
import (
|
||||||
// Cloud providers
|
// Cloud providers
|
||||||
_ "k8s.io/legacy-cloud-providers/gce"
|
_ "k8s.io/legacy-cloud-providers/gce"
|
||||||
_ "k8s.io/legacy-cloud-providers/vsphere"
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2019 The Kubernetes 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 cloudprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
// transitive test dependencies are not vendored by go modules
|
|
||||||
// so we have to explicitly import them here
|
|
||||||
_ "k8s.io/legacy-cloud-providers/vsphere/testing"
|
|
||||||
)
|
|
||||||
@@ -41,7 +41,6 @@ var (
|
|||||||
detail string
|
detail string
|
||||||
}{
|
}{
|
||||||
{"gce", false, "The GCE provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-gcp"},
|
{"gce", false, "The GCE provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-gcp"},
|
||||||
{"vsphere", false, "The vSphere provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-vsphere"},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ require (
|
|||||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b
|
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/vmware/govmomi v0.30.6
|
|
||||||
golang.org/x/oauth2 v0.10.0
|
golang.org/x/oauth2 v0.10.0
|
||||||
google.golang.org/api v0.126.0
|
google.golang.org/api v0.126.0
|
||||||
gopkg.in/gcfg.v1 v1.2.3
|
gopkg.in/gcfg.v1 v1.2.3
|
||||||
@@ -72,7 +71,6 @@ require (
|
|||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/component-helpers v0.0.0 // indirect
|
|
||||||
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e // indirect
|
k8s.io/kube-openapi v0.0.0-20231113174909-778a5567bc1e // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
|
|||||||
6
staging/src/k8s.io/legacy-cloud-providers/go.sum
generated
6
staging/src/k8s.io/legacy-cloud-providers/go.sum
generated
@@ -55,7 +55,6 @@ github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f1181
|
|||||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b/go.mod h1:FNj4KYEAAHfYu68kRYolGoxkaJn+6mdEsaM12VTwuI0=
|
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b/go.mod h1:FNj4KYEAAHfYu68kRYolGoxkaJn+6mdEsaM12VTwuI0=
|
||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
|
|
||||||
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
@@ -88,7 +87,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02/go.mod h1:7NQ3kWOx2cZOSjtcveTa5nqupVr2s6/83sG+rTlI7uA=
|
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.11.0/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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@@ -281,7 +279,6 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO
|
|||||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
github.com/rasky/go-xdr v0.0.0-20170217172119-4930550ba2e2/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o=
|
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
@@ -303,9 +300,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/vmware/govmomi v0.30.6 h1:O3tjSwQBy0XwI5uK1/yVIfQ1LP9bAECEDUfifnyGs9U=
|
|
||||||
github.com/vmware/govmomi v0.30.6/go.mod h1:epgoslm97rLECMV4D+08ORzUBEU7boFSepKjt7AYVGg=
|
|
||||||
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
|
|
||||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# See the OWNERS docs at https://go.k8s.io/owners
|
|
||||||
|
|
||||||
emeritus_approvers:
|
|
||||||
- baludontu
|
|
||||||
- divyenpatel
|
|
||||||
- frapposelli
|
|
||||||
- dougm
|
|
||||||
- SandeepPissay
|
|
||||||
- imkin
|
|
||||||
- abrarshivani
|
|
||||||
reviewers:
|
|
||||||
- divyenpatel
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
v1 "k8s.io/client-go/listers/core/v1"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error Messages
|
|
||||||
const (
|
|
||||||
CredentialsNotFoundErrMsg = "Credentials not found"
|
|
||||||
CredentialMissingErrMsg = "Username/Password is missing"
|
|
||||||
UnknownSecretKeyErrMsg = "Unknown secret key"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error constants
|
|
||||||
var (
|
|
||||||
ErrCredentialsNotFound = errors.New(CredentialsNotFoundErrMsg)
|
|
||||||
ErrCredentialMissing = errors.New(CredentialMissingErrMsg)
|
|
||||||
ErrUnknownSecretKey = errors.New(UnknownSecretKeyErrMsg)
|
|
||||||
)
|
|
||||||
|
|
||||||
type SecretCache struct {
|
|
||||||
cacheLock sync.Mutex
|
|
||||||
VirtualCenter map[string]*Credential
|
|
||||||
Secret *corev1.Secret
|
|
||||||
}
|
|
||||||
|
|
||||||
type Credential struct {
|
|
||||||
User string `gcfg:"user"`
|
|
||||||
Password string `gcfg:"password" datapolicy:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecretCredentialManager struct {
|
|
||||||
SecretName string
|
|
||||||
SecretNamespace string
|
|
||||||
SecretLister v1.SecretLister
|
|
||||||
Cache *SecretCache
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCredential returns credentials for the given vCenter Server.
|
|
||||||
// GetCredential returns error if Secret is not added.
|
|
||||||
// GetCredential return error is the secret doesn't contain any credentials.
|
|
||||||
func (secretCredentialManager *SecretCredentialManager) GetCredential(server string) (*Credential, error) {
|
|
||||||
err := secretCredentialManager.updateCredentialsMap()
|
|
||||||
if err != nil {
|
|
||||||
statusErr, ok := err.(*apierrors.StatusError)
|
|
||||||
if (ok && statusErr.ErrStatus.Code != http.StatusNotFound) || !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Handle secrets deletion by finding credentials from cache
|
|
||||||
klog.Warningf("secret %q not found in namespace %q", secretCredentialManager.SecretName, secretCredentialManager.SecretNamespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converting server FQIN to lowercase to consolidate with config parsing approach
|
|
||||||
server = strings.ToLower(server)
|
|
||||||
credential, found := secretCredentialManager.Cache.GetCredential(server)
|
|
||||||
if !found {
|
|
||||||
klog.Errorf("credentials not found for server %q", server)
|
|
||||||
return nil, ErrCredentialsNotFound
|
|
||||||
}
|
|
||||||
return &credential, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (secretCredentialManager *SecretCredentialManager) updateCredentialsMap() error {
|
|
||||||
if secretCredentialManager.SecretLister == nil {
|
|
||||||
return fmt.Errorf("secretLister is not initialized")
|
|
||||||
}
|
|
||||||
secret, err := secretCredentialManager.SecretLister.Secrets(secretCredentialManager.SecretNamespace).Get(secretCredentialManager.SecretName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Cannot get secret %s in namespace %s. error: %q", secretCredentialManager.SecretName, secretCredentialManager.SecretNamespace, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cacheSecret := secretCredentialManager.Cache.GetSecret()
|
|
||||||
if cacheSecret != nil &&
|
|
||||||
cacheSecret.GetResourceVersion() == secret.GetResourceVersion() {
|
|
||||||
klog.V(4).Infof("VCP SecretCredentialManager: Secret %q will not be updated in cache. Since, secrets have same resource version %q", secretCredentialManager.SecretName, cacheSecret.GetResourceVersion())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
secretCredentialManager.Cache.UpdateSecret(secret)
|
|
||||||
return secretCredentialManager.Cache.parseSecret()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *SecretCache) GetSecret() *corev1.Secret {
|
|
||||||
cache.cacheLock.Lock()
|
|
||||||
defer cache.cacheLock.Unlock()
|
|
||||||
return cache.Secret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *SecretCache) UpdateSecret(secret *corev1.Secret) {
|
|
||||||
cache.cacheLock.Lock()
|
|
||||||
defer cache.cacheLock.Unlock()
|
|
||||||
cache.Secret = secret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *SecretCache) GetCredential(server string) (Credential, bool) {
|
|
||||||
cache.cacheLock.Lock()
|
|
||||||
defer cache.cacheLock.Unlock()
|
|
||||||
credential, found := cache.VirtualCenter[server]
|
|
||||||
if !found {
|
|
||||||
return Credential{}, found
|
|
||||||
}
|
|
||||||
return *credential, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *SecretCache) parseSecret() error {
|
|
||||||
cache.cacheLock.Lock()
|
|
||||||
defer cache.cacheLock.Unlock()
|
|
||||||
return parseConfig(cache.Secret.Data, cache.VirtualCenter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseConfig returns vCenter ip/fdqn mapping to its credentials viz. Username and Password.
|
|
||||||
func parseConfig(data map[string][]byte, config map[string]*Credential) error {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return ErrCredentialMissing
|
|
||||||
}
|
|
||||||
for credentialKey, credentialValue := range data {
|
|
||||||
credentialKey = strings.ToLower(credentialKey)
|
|
||||||
vcServer := ""
|
|
||||||
if strings.HasSuffix(credentialKey, "password") {
|
|
||||||
vcServer = strings.Split(credentialKey, ".password")[0]
|
|
||||||
if _, ok := config[vcServer]; !ok {
|
|
||||||
config[vcServer] = &Credential{}
|
|
||||||
}
|
|
||||||
config[vcServer].Password = string(credentialValue)
|
|
||||||
} else if strings.HasSuffix(credentialKey, "username") {
|
|
||||||
vcServer = strings.Split(credentialKey, ".username")[0]
|
|
||||||
if _, ok := config[vcServer]; !ok {
|
|
||||||
config[vcServer] = &Credential{}
|
|
||||||
}
|
|
||||||
config[vcServer].User = string(credentialValue)
|
|
||||||
} else {
|
|
||||||
klog.Errorf("Unknown secret key %s", credentialKey)
|
|
||||||
return ErrUnknownSecretKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for vcServer, credential := range config {
|
|
||||||
if credential.User == "" || credential.Password == "" {
|
|
||||||
klog.Errorf("Username/Password is missing for server %s", vcServer)
|
|
||||||
return ErrCredentialMissing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/client-go/informers"
|
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSecretCredentialManager_GetCredential(t *testing.T) {
|
|
||||||
var (
|
|
||||||
userKey = "username"
|
|
||||||
passwordKey = "password"
|
|
||||||
testUser = "user"
|
|
||||||
testPassword = "password"
|
|
||||||
testServer = "0.0.0.0"
|
|
||||||
testServer2 = "0.0.1.1"
|
|
||||||
testServerFQIN = "ExAmple.com"
|
|
||||||
testUserServer2 = "user1"
|
|
||||||
testPasswordServer2 = "password1"
|
|
||||||
testIncorrectServer = "1.1.1.1"
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
secretName = "vsconf"
|
|
||||||
secretNamespace = "kube-system"
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
addSecretOp = "ADD_SECRET_OP"
|
|
||||||
getCredentialsOp = "GET_CREDENTIAL_OP"
|
|
||||||
deleteSecretOp = "DELETE_SECRET_OP"
|
|
||||||
)
|
|
||||||
type GetCredentialsTest struct {
|
|
||||||
server string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
type OpSecretTest struct {
|
|
||||||
secret *corev1.Secret
|
|
||||||
}
|
|
||||||
type testEnv struct {
|
|
||||||
testName string
|
|
||||||
ops []string
|
|
||||||
expectedValues []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &fake.Clientset{}
|
|
||||||
metaObj := metav1.ObjectMeta{
|
|
||||||
Name: secretName,
|
|
||||||
Namespace: secretNamespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultSecret := &corev1.Secret{
|
|
||||||
ObjectMeta: metaObj,
|
|
||||||
Data: map[string][]byte{
|
|
||||||
testServer + "." + userKey: []byte(testUser),
|
|
||||||
testServer + "." + passwordKey: []byte(testPassword),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
multiVCSecret := &corev1.Secret{
|
|
||||||
ObjectMeta: metaObj,
|
|
||||||
Data: map[string][]byte{
|
|
||||||
testServer + "." + userKey: []byte(testUser),
|
|
||||||
testServer + "." + passwordKey: []byte(testPassword),
|
|
||||||
testServer2 + "." + userKey: []byte(testUserServer2),
|
|
||||||
testServer2 + "." + passwordKey: []byte(testPasswordServer2),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fqinSecret := &corev1.Secret{
|
|
||||||
ObjectMeta: metaObj,
|
|
||||||
Data: map[string][]byte{
|
|
||||||
testServerFQIN + "." + userKey: []byte(testUser),
|
|
||||||
testServerFQIN + "." + passwordKey: []byte(testPassword),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
emptySecret := &corev1.Secret{
|
|
||||||
ObjectMeta: metaObj,
|
|
||||||
Data: map[string][]byte{},
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []testEnv{
|
|
||||||
{
|
|
||||||
testName: "Deleting secret should give the credentials from cache",
|
|
||||||
ops: []string{addSecretOp, getCredentialsOp, deleteSecretOp, getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
OpSecretTest{
|
|
||||||
secret: defaultSecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
username: testUser,
|
|
||||||
password: testPassword,
|
|
||||||
server: testServer,
|
|
||||||
},
|
|
||||||
OpSecretTest{
|
|
||||||
secret: defaultSecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
username: testUser,
|
|
||||||
password: testPassword,
|
|
||||||
server: testServer,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Add secret and get credentials",
|
|
||||||
ops: []string{addSecretOp, getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
OpSecretTest{
|
|
||||||
secret: defaultSecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
username: testUser,
|
|
||||||
password: testPassword,
|
|
||||||
server: testServer,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Getcredentials should fail by not adding at secret at first time",
|
|
||||||
ops: []string{getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
GetCredentialsTest{
|
|
||||||
username: testUser,
|
|
||||||
password: testPassword,
|
|
||||||
server: testServer,
|
|
||||||
err: ErrCredentialsNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "GetCredential should fail to get credentials from empty secrets",
|
|
||||||
ops: []string{addSecretOp, getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
OpSecretTest{
|
|
||||||
secret: emptySecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
server: testServer,
|
|
||||||
err: ErrCredentialMissing,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "GetCredential should fail to get credentials for invalid server",
|
|
||||||
ops: []string{addSecretOp, getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
OpSecretTest{
|
|
||||||
secret: defaultSecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
server: testIncorrectServer,
|
|
||||||
err: ErrCredentialsNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "GetCredential for multi-vc",
|
|
||||||
ops: []string{addSecretOp, getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
OpSecretTest{
|
|
||||||
secret: multiVCSecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
server: testServer2,
|
|
||||||
username: testUserServer2,
|
|
||||||
password: testPasswordServer2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "GetCredential for FQIN server name",
|
|
||||||
ops: []string{addSecretOp, getCredentialsOp},
|
|
||||||
expectedValues: []interface{}{
|
|
||||||
OpSecretTest{
|
|
||||||
fqinSecret,
|
|
||||||
},
|
|
||||||
GetCredentialsTest{
|
|
||||||
username: testUser,
|
|
||||||
password: testPassword,
|
|
||||||
server: testServerFQIN,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace 0 with NoResyncPeriodFunc() once it moved out pkg/controller/controller_utils.go in k/k.
|
|
||||||
informerFactory := informers.NewSharedInformerFactory(client, 0)
|
|
||||||
secretInformer := informerFactory.Core().V1().Secrets()
|
|
||||||
secretCredentialManager := &SecretCredentialManager{
|
|
||||||
SecretName: secretName,
|
|
||||||
SecretNamespace: secretNamespace,
|
|
||||||
SecretLister: secretInformer.Lister(),
|
|
||||||
Cache: &SecretCache{
|
|
||||||
VirtualCenter: make(map[string]*Credential),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cleanupSecretCredentialManager := func() {
|
|
||||||
secretCredentialManager.Cache.Secret = nil
|
|
||||||
for key := range secretCredentialManager.Cache.VirtualCenter {
|
|
||||||
delete(secretCredentialManager.Cache.VirtualCenter, key)
|
|
||||||
}
|
|
||||||
secrets, err := secretCredentialManager.SecretLister.List(labels.Everything())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to get all secrets from sharedInformer. error: ", err)
|
|
||||||
}
|
|
||||||
for _, secret := range secrets {
|
|
||||||
err := secretInformer.Informer().GetIndexer().Delete(secret)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to delete secret from informer: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Logf("Executing Testcase: %s", test.testName)
|
|
||||||
for ntest, op := range test.ops {
|
|
||||||
switch op {
|
|
||||||
case addSecretOp:
|
|
||||||
expected := test.expectedValues[ntest].(OpSecretTest)
|
|
||||||
t.Logf("Adding secret: %s", expected.secret)
|
|
||||||
err := secretInformer.Informer().GetIndexer().Add(expected.secret)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to add secret to internal cache: %v", err)
|
|
||||||
}
|
|
||||||
case getCredentialsOp:
|
|
||||||
expected := test.expectedValues[ntest].(GetCredentialsTest)
|
|
||||||
credential, err := secretCredentialManager.GetCredential(expected.server)
|
|
||||||
t.Logf("Retrieving credentials for server %s", expected.server)
|
|
||||||
if err != expected.err {
|
|
||||||
t.Fatalf("Fail to get credentials with error: %v", err)
|
|
||||||
}
|
|
||||||
if expected.err == nil {
|
|
||||||
if expected.username != credential.User ||
|
|
||||||
expected.password != credential.Password {
|
|
||||||
t.Fatalf("Received credentials %v "+
|
|
||||||
"are different than actual credential user:%s password:%s", credential, expected.username,
|
|
||||||
expected.password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case deleteSecretOp:
|
|
||||||
expected := test.expectedValues[ntest].(OpSecretTest)
|
|
||||||
t.Logf("Deleting secret: %s", expected.secret)
|
|
||||||
err := secretInformer.Informer().GetIndexer().Delete(expected.secret)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to delete secret to internal cache: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleanupSecretCredentialManager()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseSecretConfig(t *testing.T) {
|
|
||||||
var (
|
|
||||||
testUsername = "Admin"
|
|
||||||
testPassword = "Password"
|
|
||||||
testIP = "10.20.30.40"
|
|
||||||
testServerFQIN = "ExAmple.com"
|
|
||||||
)
|
|
||||||
var testcases = []struct {
|
|
||||||
testName string
|
|
||||||
data map[string][]byte
|
|
||||||
config map[string]*Credential
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
testName: "Valid username and password",
|
|
||||||
data: map[string][]byte{
|
|
||||||
"10.20.30.40.username": []byte(testUsername),
|
|
||||||
"10.20.30.40.password": []byte(testPassword),
|
|
||||||
},
|
|
||||||
config: map[string]*Credential{
|
|
||||||
testIP: {
|
|
||||||
User: testUsername,
|
|
||||||
Password: testPassword,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Invalid username key with valid password key",
|
|
||||||
data: map[string][]byte{
|
|
||||||
"10.20.30.40.usernam": []byte(testUsername),
|
|
||||||
"10.20.30.40.password": []byte(testPassword),
|
|
||||||
},
|
|
||||||
config: nil,
|
|
||||||
expectedError: ErrUnknownSecretKey,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Missing username",
|
|
||||||
data: map[string][]byte{
|
|
||||||
"10.20.30.40.password": []byte(testPassword),
|
|
||||||
},
|
|
||||||
config: map[string]*Credential{
|
|
||||||
testIP: {
|
|
||||||
Password: testPassword,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: ErrCredentialMissing,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Missing password",
|
|
||||||
data: map[string][]byte{
|
|
||||||
"10.20.30.40.username": []byte(testUsername),
|
|
||||||
},
|
|
||||||
config: map[string]*Credential{
|
|
||||||
testIP: {
|
|
||||||
User: testUsername,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: ErrCredentialMissing,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "FQIN stored as lowercase",
|
|
||||||
data: map[string][]byte{
|
|
||||||
testServerFQIN + ".username": []byte(testUsername),
|
|
||||||
testServerFQIN + ".password": []byte(testPassword),
|
|
||||||
},
|
|
||||||
config: map[string]*Credential{
|
|
||||||
strings.ToLower(testServerFQIN): {
|
|
||||||
User: testUsername,
|
|
||||||
Password: testPassword,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "IP with unknown key",
|
|
||||||
data: map[string][]byte{
|
|
||||||
"10.20.30.40": []byte(testUsername),
|
|
||||||
},
|
|
||||||
config: nil,
|
|
||||||
expectedError: ErrUnknownSecretKey,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resultConfig := make(map[string]*Credential)
|
|
||||||
cleanupResultConfig := func(config map[string]*Credential) {
|
|
||||||
for k := range config {
|
|
||||||
delete(config, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testcase := range testcases {
|
|
||||||
err := parseConfig(testcase.data, resultConfig)
|
|
||||||
t.Logf("Executing Testcase: %s", testcase.testName)
|
|
||||||
if err != testcase.expectedError {
|
|
||||||
t.Fatalf("Parsing Secret failed for data %+v: %s", testcase.data, err)
|
|
||||||
}
|
|
||||||
if testcase.config != nil && !reflect.DeepEqual(testcase.config, resultConfig) {
|
|
||||||
t.Fatalf("Parsing Secret failed for data %+v expected config %+v and actual config %+v",
|
|
||||||
testcase.data, resultConfig, testcase.config)
|
|
||||||
}
|
|
||||||
cleanupResultConfig(resultConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2019 The Kubernetes 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 vsphere
|
|
||||||
@@ -1,564 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
|
||||||
coreclients "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
||||||
corelisters "k8s.io/client-go/listers/core/v1"
|
|
||||||
cloudprovider "k8s.io/cloud-provider"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stores info about the kubernetes node
|
|
||||||
type NodeInfo struct {
|
|
||||||
dataCenter *vclib.Datacenter
|
|
||||||
vm *vclib.VirtualMachine
|
|
||||||
vcServer string
|
|
||||||
vmUUID string
|
|
||||||
zone *cloudprovider.Zone
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n NodeInfo) String() string {
|
|
||||||
return fmt.Sprintf("{datacenter: %v, vm: %v, vcServer: %s, vmUUID: %s, zone: %v}",
|
|
||||||
*n.dataCenter, n.vm.Reference(), n.vcServer, n.vmUUID, *n.zone)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeManager struct {
|
|
||||||
// TODO: replace map with concurrent map when k8s supports go v1.9
|
|
||||||
|
|
||||||
// Maps the VC server to VSphereInstance
|
|
||||||
vsphereInstanceMap map[string]*VSphereInstance
|
|
||||||
// Maps node name to node info.
|
|
||||||
nodeInfoMap map[string]*NodeInfo
|
|
||||||
// Maps node name to node structure
|
|
||||||
registeredNodes map[string]*v1.Node
|
|
||||||
//CredentialsManager
|
|
||||||
credentialManager *SecretCredentialManager
|
|
||||||
|
|
||||||
nodeLister corelisters.NodeLister
|
|
||||||
nodeGetter coreclients.NodesGetter
|
|
||||||
|
|
||||||
// Mutexes
|
|
||||||
registeredNodesLock sync.RWMutex
|
|
||||||
nodeInfoLock sync.RWMutex
|
|
||||||
credentialManagerLock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeDetails struct {
|
|
||||||
NodeName string
|
|
||||||
vm *vclib.VirtualMachine
|
|
||||||
VMUUID string
|
|
||||||
Zone *cloudprovider.Zone
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make it configurable in vsphere.conf
|
|
||||||
const (
|
|
||||||
POOL_SIZE = 8
|
|
||||||
QUEUE_SIZE = POOL_SIZE * 10
|
|
||||||
)
|
|
||||||
|
|
||||||
func (nm *NodeManager) DiscoverNode(node *v1.Node) error {
|
|
||||||
type VmSearch struct {
|
|
||||||
vc string
|
|
||||||
datacenter *vclib.Datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
var mutex = &sync.Mutex{}
|
|
||||||
var globalErrMutex = &sync.Mutex{}
|
|
||||||
var queueChannel chan *VmSearch
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var globalErr *error
|
|
||||||
|
|
||||||
queueChannel = make(chan *VmSearch, QUEUE_SIZE)
|
|
||||||
nodeUUID, err := GetNodeUUID(node)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Node Discovery failed to get node uuid for node %s with error: %v", node.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(4).Infof("Discovering node %s with uuid %s", node.ObjectMeta.Name, nodeUUID)
|
|
||||||
|
|
||||||
vmFound := false
|
|
||||||
globalErr = nil
|
|
||||||
|
|
||||||
setGlobalErr := func(err error) {
|
|
||||||
globalErrMutex.Lock()
|
|
||||||
globalErr = &err
|
|
||||||
globalErrMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
setVMFound := func(found bool) {
|
|
||||||
mutex.Lock()
|
|
||||||
vmFound = found
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
getVMFound := func() bool {
|
|
||||||
mutex.Lock()
|
|
||||||
found := vmFound
|
|
||||||
mutex.Unlock()
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
var datacenterObjs []*vclib.Datacenter
|
|
||||||
for vc, vsi := range nm.vsphereInstanceMap {
|
|
||||||
|
|
||||||
found := getVMFound()
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := nm.vcConnect(ctx, vsi)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Info("Discovering node error vc:", err)
|
|
||||||
setGlobalErr(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if vsi.cfg.Datacenters == "" {
|
|
||||||
datacenterObjs, err = vclib.GetAllDatacenter(ctx, vsi.conn)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Info("Discovering node error dc:", err)
|
|
||||||
setGlobalErr(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
datacenters := strings.Split(vsi.cfg.Datacenters, ",")
|
|
||||||
for _, dc := range datacenters {
|
|
||||||
dc = strings.TrimSpace(dc)
|
|
||||||
if dc == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datacenterObj, err := vclib.GetDatacenter(ctx, vsi.conn, dc)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Info("Discovering node error dc:", err)
|
|
||||||
setGlobalErr(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datacenterObjs = append(datacenterObjs, datacenterObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, datacenterObj := range datacenterObjs {
|
|
||||||
found := getVMFound()
|
|
||||||
if found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(4).Infof("Finding node %s in vc=%s and datacenter=%s", node.Name, vc, datacenterObj.Name())
|
|
||||||
queueChannel <- &VmSearch{
|
|
||||||
vc: vc,
|
|
||||||
datacenter: datacenterObj,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(queueChannel)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i := 0; i < POOL_SIZE; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
for res := range queueChannel {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
vm, err := res.datacenter.GetVMByUUID(ctx, nodeUUID)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Error while looking for vm=%+v in vc=%s and datacenter=%s: %v",
|
|
||||||
vm, res.vc, res.datacenter.Name(), err)
|
|
||||||
if err != vclib.ErrNoVMFound {
|
|
||||||
setGlobalErr(err)
|
|
||||||
} else {
|
|
||||||
klog.V(4).Infof("Did not find node %s in vc=%s and datacenter=%s",
|
|
||||||
node.Name, res.vc, res.datacenter.Name())
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if vm != nil {
|
|
||||||
klog.V(4).Infof("Found node %s as vm=%+v in vc=%s and datacenter=%s",
|
|
||||||
node.Name, vm, res.vc, res.datacenter.Name())
|
|
||||||
var vmObj mo.VirtualMachine
|
|
||||||
err := vm.Properties(ctx, vm.Reference(), []string{"config"}, &vmObj)
|
|
||||||
if err != nil || vmObj.Config == nil {
|
|
||||||
klog.Errorf("failed to retrieve guest vmconfig for node: %s Err: %v", node.Name, err)
|
|
||||||
} else {
|
|
||||||
klog.V(4).Infof("vm hardware version for node:%s is %s", node.Name, vmObj.Config.Version)
|
|
||||||
// vmconfig.Version returns vm hardware version as vmx-11, vmx-13, vmx-14, vmx-15 etc.
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated(vmObj.Config.Version)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("failed to check if vm hardware version is deprecated. VM Hardware Version: %s Err: %v", vmObj.Config.Version, err)
|
|
||||||
}
|
|
||||||
if vmhardwaredeprecated {
|
|
||||||
klog.Warningf("VM Hardware version: %s from node: %s is deprecated. Please consider upgrading virtual machine hardware version to vmx-15 or higher", vmObj.Config.Version, node.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get the node zone information
|
|
||||||
nodeFd := node.ObjectMeta.Labels[v1.LabelTopologyZone]
|
|
||||||
nodeRegion := node.ObjectMeta.Labels[v1.LabelTopologyRegion]
|
|
||||||
nodeZone := &cloudprovider.Zone{FailureDomain: nodeFd, Region: nodeRegion}
|
|
||||||
nodeInfo := &NodeInfo{dataCenter: res.datacenter, vm: vm, vcServer: res.vc, vmUUID: nodeUUID, zone: nodeZone}
|
|
||||||
nm.addNodeInfo(node.ObjectMeta.Name, nodeInfo)
|
|
||||||
for range queueChannel {
|
|
||||||
}
|
|
||||||
setVMFound(true)
|
|
||||||
cancel()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
if vmFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if globalErr != nil {
|
|
||||||
return *globalErr
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(4).Infof("Discovery Node: %q vm not found", node.Name)
|
|
||||||
return vclib.ErrNoVMFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) RegisterNode(node *v1.Node) error {
|
|
||||||
nm.addNode(node)
|
|
||||||
return nm.DiscoverNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) UnRegisterNode(node *v1.Node) error {
|
|
||||||
nm.removeNode(node)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) RediscoverNode(nodeName k8stypes.NodeName) error {
|
|
||||||
node, err := nm.GetNode(nodeName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nm.DiscoverNode(&node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) GetNode(nodeName k8stypes.NodeName) (v1.Node, error) {
|
|
||||||
nm.registeredNodesLock.RLock()
|
|
||||||
node := nm.registeredNodes[convertToString(nodeName)]
|
|
||||||
nm.registeredNodesLock.RUnlock()
|
|
||||||
if node != nil {
|
|
||||||
klog.V(4).Infof("Node %s found in vSphere cloud provider cache", nodeName)
|
|
||||||
return *node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if nm.nodeLister != nil {
|
|
||||||
klog.V(4).Infof("Node %s missing in vSphere cloud provider cache, trying node informer", nodeName)
|
|
||||||
node, err := nm.nodeLister.Get(convertToString(nodeName))
|
|
||||||
if err != nil {
|
|
||||||
if !errors.IsNotFound(err) {
|
|
||||||
return v1.Node{}, err
|
|
||||||
}
|
|
||||||
// Fall through with IsNotFound error and try to get the node from the API server
|
|
||||||
} else {
|
|
||||||
node := node.DeepCopy()
|
|
||||||
nm.addNode(node)
|
|
||||||
klog.V(4).Infof("Node %s found in vSphere cloud provider node informer", nodeName)
|
|
||||||
return *node, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nm.nodeGetter != nil {
|
|
||||||
klog.V(4).Infof("Node %s missing in vSphere cloud provider caches, trying the API server", nodeName)
|
|
||||||
node, err := nm.nodeGetter.Nodes().Get(context.TODO(), convertToString(nodeName), metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
if !errors.IsNotFound(err) {
|
|
||||||
return v1.Node{}, err
|
|
||||||
}
|
|
||||||
// Fall through with IsNotFound error to keep the code consistent with the above
|
|
||||||
} else {
|
|
||||||
nm.addNode(node)
|
|
||||||
klog.V(4).Infof("Node %s found in the API server", nodeName)
|
|
||||||
return *node, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Node %s not found in vSphere cloud provider", nodeName)
|
|
||||||
return v1.Node{}, vclib.ErrNoVMFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) getNodes() map[string]*v1.Node {
|
|
||||||
nm.registeredNodesLock.RLock()
|
|
||||||
defer nm.registeredNodesLock.RUnlock()
|
|
||||||
registeredNodes := make(map[string]*v1.Node, len(nm.registeredNodes))
|
|
||||||
for nodeName, node := range nm.registeredNodes {
|
|
||||||
registeredNodes[nodeName] = node
|
|
||||||
}
|
|
||||||
return registeredNodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) addNode(node *v1.Node) {
|
|
||||||
nm.registeredNodesLock.Lock()
|
|
||||||
nm.registeredNodes[node.ObjectMeta.Name] = node
|
|
||||||
nm.registeredNodesLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) removeNode(node *v1.Node) {
|
|
||||||
nm.registeredNodesLock.Lock()
|
|
||||||
delete(nm.registeredNodes, node.ObjectMeta.Name)
|
|
||||||
nm.registeredNodesLock.Unlock()
|
|
||||||
|
|
||||||
nm.nodeInfoLock.Lock()
|
|
||||||
delete(nm.nodeInfoMap, node.ObjectMeta.Name)
|
|
||||||
nm.nodeInfoLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeInfo returns a NodeInfo which datacenter, vm and vc server ip address.
|
|
||||||
// This method returns an error if it is unable find node VCs and DCs listed in vSphere.conf
|
|
||||||
// NodeInfo returned may not be updated to reflect current VM location.
|
|
||||||
//
|
|
||||||
// This method is a getter but it can cause side-effect of updating NodeInfo object.
|
|
||||||
func (nm *NodeManager) GetNodeInfo(nodeName k8stypes.NodeName) (NodeInfo, error) {
|
|
||||||
return nm.getRefreshedNodeInfo(nodeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeDetails returns NodeDetails for all the discovered nodes.
|
|
||||||
//
|
|
||||||
// This method is a getter but it can cause side-effect of updating NodeInfo objects.
|
|
||||||
func (nm *NodeManager) GetNodeDetails() ([]NodeDetails, error) {
|
|
||||||
var nodeDetails []NodeDetails
|
|
||||||
|
|
||||||
for nodeName, nodeObj := range nm.getNodes() {
|
|
||||||
nodeInfo, err := nm.GetNodeInfoWithNodeObject(nodeObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Updated NodeInfo %v for node %q.", nodeInfo, nodeName)
|
|
||||||
nodeDetails = append(nodeDetails, NodeDetails{nodeName, nodeInfo.vm, nodeInfo.vmUUID, nodeInfo.zone})
|
|
||||||
}
|
|
||||||
return nodeDetails, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeNames returns list of nodes that are known to vsphere cloudprovider.
|
|
||||||
// These are typically nodes that make up k8s cluster.
|
|
||||||
func (nm *NodeManager) GetNodeNames() []k8stypes.NodeName {
|
|
||||||
nodes := nm.getNodes()
|
|
||||||
var nodeNameList []k8stypes.NodeName
|
|
||||||
for _, node := range nodes {
|
|
||||||
nodeNameList = append(nodeNameList, k8stypes.NodeName(node.Name))
|
|
||||||
}
|
|
||||||
return nodeNameList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) refreshNodes() (errList []error) {
|
|
||||||
for nodeName := range nm.getNodes() {
|
|
||||||
nodeInfo, err := nm.getRefreshedNodeInfo(convertToK8sType(nodeName))
|
|
||||||
if err != nil {
|
|
||||||
errList = append(errList, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Updated NodeInfo %v for node %q.", nodeInfo, nodeName)
|
|
||||||
}
|
|
||||||
return errList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) getRefreshedNodeInfo(nodeName k8stypes.NodeName) (NodeInfo, error) {
|
|
||||||
nodeInfo := nm.getNodeInfo(nodeName)
|
|
||||||
var err error
|
|
||||||
if nodeInfo == nil {
|
|
||||||
// Rediscover node if no NodeInfo found.
|
|
||||||
klog.V(4).Infof("No VM found for node %q. Initiating rediscovery.", convertToString(nodeName))
|
|
||||||
err = nm.RediscoverNode(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error %q node info for node %q not found", err, convertToString(nodeName))
|
|
||||||
return NodeInfo{}, err
|
|
||||||
}
|
|
||||||
nodeInfo = nm.getNodeInfo(nodeName)
|
|
||||||
} else {
|
|
||||||
// Renew the found NodeInfo to avoid stale vSphere connection.
|
|
||||||
klog.V(4).Infof("Renewing NodeInfo %+v for node %q", nodeInfo, convertToString(nodeName))
|
|
||||||
nodeInfo, err = nm.renewNodeInfo(nodeInfo, true)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error %q occurred while renewing NodeInfo for %q", err, convertToString(nodeName))
|
|
||||||
return NodeInfo{}, err
|
|
||||||
}
|
|
||||||
nm.addNodeInfo(convertToString(nodeName), nodeInfo)
|
|
||||||
}
|
|
||||||
return *nodeInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) addNodeInfo(nodeName string, nodeInfo *NodeInfo) {
|
|
||||||
nm.nodeInfoLock.Lock()
|
|
||||||
nm.nodeInfoMap[nodeName] = nodeInfo
|
|
||||||
nm.nodeInfoLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) getNodeInfo(nodeName k8stypes.NodeName) *NodeInfo {
|
|
||||||
nm.nodeInfoLock.RLock()
|
|
||||||
nodeInfo := nm.nodeInfoMap[convertToString(nodeName)]
|
|
||||||
nm.nodeInfoLock.RUnlock()
|
|
||||||
return nodeInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) GetVSphereInstance(nodeName k8stypes.NodeName) (VSphereInstance, error) {
|
|
||||||
nodeInfo, err := nm.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("node info for node %q not found", convertToString(nodeName))
|
|
||||||
return VSphereInstance{}, err
|
|
||||||
}
|
|
||||||
vsphereInstance := nm.vsphereInstanceMap[nodeInfo.vcServer]
|
|
||||||
if vsphereInstance == nil {
|
|
||||||
return VSphereInstance{}, fmt.Errorf("vSphereInstance for vc server %q not found while looking for node %q", nodeInfo.vcServer, convertToString(nodeName))
|
|
||||||
}
|
|
||||||
return *vsphereInstance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// renewNodeInfo renews vSphere connection, VirtualMachine and Datacenter for NodeInfo instance.
|
|
||||||
func (nm *NodeManager) renewNodeInfo(nodeInfo *NodeInfo, reconnect bool) (*NodeInfo, error) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
vsphereInstance := nm.vsphereInstanceMap[nodeInfo.vcServer]
|
|
||||||
if vsphereInstance == nil {
|
|
||||||
err := fmt.Errorf("vSphereInstance for vSphere %q not found while refershing NodeInfo for VM %q", nodeInfo.vcServer, nodeInfo.vm)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if reconnect {
|
|
||||||
err := nm.vcConnect(ctx, vsphereInstance)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vm := nodeInfo.vm.RenewVM(vsphereInstance.conn.Client)
|
|
||||||
return &NodeInfo{
|
|
||||||
vm: &vm,
|
|
||||||
dataCenter: vm.Datacenter,
|
|
||||||
vcServer: nodeInfo.vcServer,
|
|
||||||
vmUUID: nodeInfo.vmUUID,
|
|
||||||
zone: nodeInfo.zone,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nodeInfo *NodeInfo) VM() *vclib.VirtualMachine {
|
|
||||||
if nodeInfo == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nodeInfo.vm
|
|
||||||
}
|
|
||||||
|
|
||||||
// vcConnect connects to vCenter with existing credentials
|
|
||||||
// If credentials are invalid:
|
|
||||||
// 1. It will fetch credentials from credentialManager
|
|
||||||
// 2. Update the credentials
|
|
||||||
// 3. Connects again to vCenter with fetched credentials
|
|
||||||
func (nm *NodeManager) vcConnect(ctx context.Context, vsphereInstance *VSphereInstance) error {
|
|
||||||
err := vsphereInstance.conn.Connect(ctx)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
credentialManager := nm.CredentialManager()
|
|
||||||
if !vclib.IsInvalidCredentialsError(err) || credentialManager == nil {
|
|
||||||
klog.Errorf("Cannot connect to vCenter with err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(4).Infof("Invalid credentials. Cannot connect to server %q. Fetching credentials from secrets.", vsphereInstance.conn.Hostname)
|
|
||||||
|
|
||||||
// Get latest credentials from SecretCredentialManager
|
|
||||||
credentials, err := credentialManager.GetCredential(vsphereInstance.conn.Hostname)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get credentials from Secret Credential Manager with err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vsphereInstance.conn.UpdateCredentials(credentials.User, credentials.Password)
|
|
||||||
return vsphereInstance.conn.Connect(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeInfoWithNodeObject returns a NodeInfo which datacenter, vm and vc server ip address.
|
|
||||||
// This method returns an error if it is unable find node VCs and DCs listed in vSphere.conf
|
|
||||||
// NodeInfo returned may not be updated to reflect current VM location.
|
|
||||||
//
|
|
||||||
// This method is a getter but it can cause side-effect of updating NodeInfo object.
|
|
||||||
func (nm *NodeManager) GetNodeInfoWithNodeObject(node *v1.Node) (NodeInfo, error) {
|
|
||||||
return nm.getRefreshedNodeInfo(convertToK8sType(node.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) CredentialManager() *SecretCredentialManager {
|
|
||||||
nm.credentialManagerLock.Lock()
|
|
||||||
defer nm.credentialManagerLock.Unlock()
|
|
||||||
return nm.credentialManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) UpdateCredentialManager(credentialManager *SecretCredentialManager) {
|
|
||||||
nm.credentialManagerLock.Lock()
|
|
||||||
defer nm.credentialManagerLock.Unlock()
|
|
||||||
nm.credentialManager = credentialManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) GetHostsInZone(ctx context.Context, zoneFailureDomain string) ([]*object.HostSystem, error) {
|
|
||||||
klog.V(9).Infof("GetHostsInZone called with registeredNodes: %v", nm.registeredNodes)
|
|
||||||
nodeDetails, err := nm.GetNodeDetails()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Node Details: %v", nodeDetails)
|
|
||||||
// Build a map of Host moRef to HostSystem
|
|
||||||
hostMap := make(map[string]*object.HostSystem)
|
|
||||||
for _, n := range nodeDetails {
|
|
||||||
// Match the provided zone failure domain with the node.
|
|
||||||
klog.V(9).Infof("Matching provided zone %s with node %s zone %s", zoneFailureDomain, n.NodeName, n.Zone.FailureDomain)
|
|
||||||
if zoneFailureDomain == n.Zone.FailureDomain {
|
|
||||||
host, err := n.vm.HostSystem(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get host system for VM %s. err: %+v", n.vm, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hostMap[host.Reference().Value] = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build the unique list of hosts.
|
|
||||||
hosts := make([]*object.HostSystem, 0)
|
|
||||||
for _, value := range hostMap {
|
|
||||||
hosts = append(hosts, value)
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("GetHostsInZone %v returning: %v", zoneFailureDomain, hosts)
|
|
||||||
return hosts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) SetNodeLister(nodeLister corelisters.NodeLister) {
|
|
||||||
nm.nodeLister = nodeLister
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *NodeManager) SetNodeGetter(nodeGetter coreclients.NodesGetter) {
|
|
||||||
nm.nodeGetter = nodeGetter
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2023 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/client-go/informers"
|
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Annotation used to distinguish nodes in node cache / informer / API server
|
|
||||||
const nodeAnnotation = "test"
|
|
||||||
|
|
||||||
func getNode(annotation string) *v1.Node {
|
|
||||||
return &v1.Node{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "node1",
|
|
||||||
Annotations: map[string]string{
|
|
||||||
nodeAnnotation: annotation,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetNode(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
cachedNodes []*v1.Node
|
|
||||||
informerNodes []*v1.Node // "nil" means that the NodeManager has no nodeLister
|
|
||||||
apiServerNodes []*v1.Node // "nil" means that the NodeManager has no nodeGetter
|
|
||||||
|
|
||||||
expectedNodeAnnotation string
|
|
||||||
expectNotFound bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "No cached node anywhere",
|
|
||||||
cachedNodes: []*v1.Node{},
|
|
||||||
informerNodes: []*v1.Node{},
|
|
||||||
apiServerNodes: []*v1.Node{},
|
|
||||||
expectNotFound: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "No lister & getter",
|
|
||||||
cachedNodes: []*v1.Node{},
|
|
||||||
informerNodes: nil,
|
|
||||||
apiServerNodes: nil,
|
|
||||||
expectNotFound: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "cache is used first",
|
|
||||||
cachedNodes: []*v1.Node{getNode("cache")},
|
|
||||||
informerNodes: []*v1.Node{getNode("informer")},
|
|
||||||
apiServerNodes: []*v1.Node{getNode("apiserver")},
|
|
||||||
expectedNodeAnnotation: "cache",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "informer is used second",
|
|
||||||
cachedNodes: []*v1.Node{},
|
|
||||||
informerNodes: []*v1.Node{getNode("informer")},
|
|
||||||
apiServerNodes: []*v1.Node{getNode("apiserver")},
|
|
||||||
expectedNodeAnnotation: "informer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "API server is used last",
|
|
||||||
cachedNodes: []*v1.Node{},
|
|
||||||
informerNodes: []*v1.Node{},
|
|
||||||
apiServerNodes: []*v1.Node{getNode("apiserver")},
|
|
||||||
expectedNodeAnnotation: "apiserver",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
|
|
||||||
// local NodeManager cache
|
|
||||||
cache := make(map[string]*v1.Node)
|
|
||||||
for _, node := range test.cachedNodes {
|
|
||||||
cache[node.Name] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client with apiServerNodes
|
|
||||||
objs := []runtime.Object{}
|
|
||||||
for _, node := range test.apiServerNodes {
|
|
||||||
objs = append(objs, node)
|
|
||||||
}
|
|
||||||
client := fake.NewSimpleClientset(objs...)
|
|
||||||
nodeGetter := client.CoreV1()
|
|
||||||
|
|
||||||
// Informer + nodeLister. Despite the client already has apiServerNodes, they won't appear in the
|
|
||||||
// nodeLister, because the informer is never started.
|
|
||||||
factory := informers.NewSharedInformerFactory(client, 0 /* no resync */)
|
|
||||||
nodeInformer := factory.Core().V1().Nodes()
|
|
||||||
for _, node := range test.informerNodes {
|
|
||||||
nodeInformer.Informer().GetStore().Add(node)
|
|
||||||
}
|
|
||||||
nodeLister := nodeInformer.Lister()
|
|
||||||
|
|
||||||
nodeManager := NodeManager{
|
|
||||||
registeredNodes: cache,
|
|
||||||
}
|
|
||||||
if test.informerNodes != nil {
|
|
||||||
nodeManager.SetNodeLister(nodeLister)
|
|
||||||
}
|
|
||||||
if test.apiServerNodes != nil {
|
|
||||||
nodeManager.SetNodeGetter(nodeGetter)
|
|
||||||
}
|
|
||||||
|
|
||||||
node, err := nodeManager.GetNode("node1")
|
|
||||||
if test.expectNotFound && err != vclib.ErrNoVMFound {
|
|
||||||
t.Errorf("Expected NotFound error, got: %v", err)
|
|
||||||
}
|
|
||||||
if !test.expectNotFound && err != nil {
|
|
||||||
t.Errorf("Unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.expectedNodeAnnotation != "" {
|
|
||||||
if node.Annotations == nil {
|
|
||||||
t.Errorf("Expected node with annotation %q, got nil", test.expectedNodeAnnotation)
|
|
||||||
} else {
|
|
||||||
if ann := node.Annotations[nodeAnnotation]; ann != test.expectedNodeAnnotation {
|
|
||||||
t.Errorf("Expected node with annotation %q, got %q", test.expectedNodeAnnotation, ann)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2021 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/property"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sharedDatastore struct {
|
|
||||||
nodeManager *NodeManager
|
|
||||||
candidateDatastores []*vclib.DatastoreInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type hostInfo struct {
|
|
||||||
hostUUID string
|
|
||||||
hostMOID string
|
|
||||||
datacenter string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
summary = "summary"
|
|
||||||
runtimeHost = "summary.runtime.host"
|
|
||||||
hostsProperty = "host"
|
|
||||||
nameProperty = "name"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (shared *sharedDatastore) getSharedDatastore(ctcx context.Context) (*vclib.DatastoreInfo, error) {
|
|
||||||
nodes := shared.nodeManager.getNodes()
|
|
||||||
|
|
||||||
// Segregate nodes according to VC-DC
|
|
||||||
dcNodes := make(map[string][]NodeInfo)
|
|
||||||
nodeHosts := make(map[string]hostInfo)
|
|
||||||
|
|
||||||
for nodeName, node := range nodes {
|
|
||||||
nodeInfo, err := shared.nodeManager.GetNodeInfoWithNodeObject(node)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to find node %s: %v", nodeName, err)
|
|
||||||
}
|
|
||||||
vcDC := nodeInfo.vcServer + nodeInfo.dataCenter.String()
|
|
||||||
dcNodes[vcDC] = append(dcNodes[vcDC], nodeInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
for vcDC, nodes := range dcNodes {
|
|
||||||
var hostInfos []hostInfo
|
|
||||||
var err error
|
|
||||||
hostInfos, err = shared.getNodeHosts(ctcx, nodes, vcDC)
|
|
||||||
if err != nil {
|
|
||||||
if vclib.IsManagedObjectNotFoundError(err) {
|
|
||||||
klog.Warningf("SharedHost.getSharedDatastore: batch fetching of hosts failed - switching to fetching them individually.")
|
|
||||||
hostInfos, err = shared.getEachNodeHost(ctcx, nodes, vcDC)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("SharedHost.getSharedDatastore: error fetching node hosts individually: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, host := range hostInfos {
|
|
||||||
hostDCName := fmt.Sprintf("%s/%s", host.datacenter, host.hostMOID)
|
|
||||||
nodeHosts[hostDCName] = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodeHosts) < 1 {
|
|
||||||
msg := fmt.Sprintf("SharedHost.getSharedDatastore unable to find hosts associated with nodes")
|
|
||||||
klog.Error(msg)
|
|
||||||
return nil, fmt.Errorf("")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, datastoreInfo := range shared.candidateDatastores {
|
|
||||||
dataStoreHosts, err := shared.getAttachedHosts(ctcx, datastoreInfo.Datastore)
|
|
||||||
if err != nil {
|
|
||||||
msg := fmt.Sprintf("error finding attached hosts to datastore %s: %v", datastoreInfo.Name(), err)
|
|
||||||
klog.Error(msg)
|
|
||||||
return nil, fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
if shared.isIncluded(dataStoreHosts, nodeHosts) {
|
|
||||||
return datastoreInfo, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("SharedHost.getSharedDatastore: unable to find any shared datastores")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if all of the nodeHosts are included in the dataStoreHosts
|
|
||||||
func (shared *sharedDatastore) isIncluded(dataStoreHosts []hostInfo, nodeHosts map[string]hostInfo) bool {
|
|
||||||
result := true
|
|
||||||
for _, host := range nodeHosts {
|
|
||||||
hostFound := false
|
|
||||||
for _, targetHost := range dataStoreHosts {
|
|
||||||
if host.hostUUID == targetHost.hostUUID && host.hostMOID == targetHost.hostMOID {
|
|
||||||
hostFound = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hostFound {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shared *sharedDatastore) getEachNodeHost(ctx context.Context, nodes []NodeInfo, dcVC string) ([]hostInfo, error) {
|
|
||||||
var hosts []hostInfo
|
|
||||||
for _, node := range nodes {
|
|
||||||
host, err := node.vm.GetHost(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("SharedHost.getEachNodeHost: unable to find host for vm %s: %v", node.vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hosts = append(hosts, hostInfo{
|
|
||||||
hostUUID: host.Summary.Hardware.Uuid,
|
|
||||||
hostMOID: host.Summary.Host.String(),
|
|
||||||
datacenter: node.dataCenter.String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return hosts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shared *sharedDatastore) getNodeHosts(ctx context.Context, nodes []NodeInfo, dcVC string) ([]hostInfo, error) {
|
|
||||||
var vmRefs []types.ManagedObjectReference
|
|
||||||
if len(nodes) < 1 {
|
|
||||||
return nil, fmt.Errorf("no nodes found for dc-vc: %s", dcVC)
|
|
||||||
}
|
|
||||||
var nodeInfo NodeInfo
|
|
||||||
for _, n := range nodes {
|
|
||||||
nodeInfo = n
|
|
||||||
vmRefs = append(vmRefs, n.vm.Reference())
|
|
||||||
}
|
|
||||||
pc := property.DefaultCollector(nodeInfo.dataCenter.Client())
|
|
||||||
var vmoList []mo.VirtualMachine
|
|
||||||
err := pc.Retrieve(ctx, vmRefs, []string{nameProperty, runtimeHost}, &vmoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("SharedHost.getNodeHosts: unable to fetch vms from datacenter %s: %v", nodeInfo.dataCenter.String(), err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hostMoList []mo.HostSystem
|
|
||||||
var hostRefs []types.ManagedObjectReference
|
|
||||||
for _, vmo := range vmoList {
|
|
||||||
if vmo.Summary.Runtime.Host == nil {
|
|
||||||
msg := fmt.Sprintf("SharedHost.getNodeHosts: no host associated with vm %s", vmo.Name)
|
|
||||||
klog.Error(msg)
|
|
||||||
return nil, fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
hostRefs = append(hostRefs, vmo.Summary.Runtime.Host.Reference())
|
|
||||||
}
|
|
||||||
pc = property.DefaultCollector(nodeInfo.dataCenter.Client())
|
|
||||||
err = pc.Retrieve(ctx, hostRefs, []string{summary}, &hostMoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("SharedHost.getNodeHosts: unable to fetch hosts from datacenter %s: %v", nodeInfo.dataCenter.String(), err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hosts []hostInfo
|
|
||||||
for _, host := range hostMoList {
|
|
||||||
hosts = append(hosts, hostInfo{hostMOID: host.Summary.Host.String(), hostUUID: host.Summary.Hardware.Uuid, datacenter: nodeInfo.dataCenter.String()})
|
|
||||||
}
|
|
||||||
return hosts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shared *sharedDatastore) getAttachedHosts(ctx context.Context, datastore *vclib.Datastore) ([]hostInfo, error) {
|
|
||||||
var ds mo.Datastore
|
|
||||||
|
|
||||||
pc := property.DefaultCollector(datastore.Client())
|
|
||||||
err := pc.RetrieveOne(ctx, datastore.Reference(), []string{hostsProperty}, &ds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mounts := make(map[types.ManagedObjectReference]types.DatastoreHostMount)
|
|
||||||
var refs []types.ManagedObjectReference
|
|
||||||
for _, host := range ds.Host {
|
|
||||||
refs = append(refs, host.Key)
|
|
||||||
mounts[host.Key] = host
|
|
||||||
}
|
|
||||||
|
|
||||||
var hs []mo.HostSystem
|
|
||||||
err = pc.Retrieve(ctx, refs, []string{summary}, &hs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hosts []hostInfo
|
|
||||||
for _, h := range hs {
|
|
||||||
hosts = append(hosts, hostInfo{hostUUID: h.Summary.Hardware.Uuid, hostMOID: h.Summary.Host.String()})
|
|
||||||
}
|
|
||||||
return hosts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2019 The Kubernetes 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 testing
|
|
||||||
|
|
||||||
import (
|
|
||||||
// test dependencies for k8s.io/legacy-cloud-providers/vsphere
|
|
||||||
// import this package to vendor test dependencies since go modules does not
|
|
||||||
// vendor transitive test dependencies
|
|
||||||
_ "github.com/vmware/govmomi/lookup/simulator"
|
|
||||||
_ "github.com/vmware/govmomi/simulator"
|
|
||||||
_ "github.com/vmware/govmomi/sts/simulator"
|
|
||||||
_ "github.com/vmware/govmomi/vapi/simulator"
|
|
||||||
)
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
neturl "net/url"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/session"
|
|
||||||
"github.com/vmware/govmomi/sts"
|
|
||||||
"github.com/vmware/govmomi/vim25"
|
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
|
||||||
"k8s.io/client-go/pkg/version"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VSphereConnection contains information for connecting to vCenter
|
|
||||||
type VSphereConnection struct {
|
|
||||||
Client *vim25.Client
|
|
||||||
Username string
|
|
||||||
Password string `datapolicy:"password"`
|
|
||||||
Hostname string
|
|
||||||
Port string
|
|
||||||
CACert string
|
|
||||||
Thumbprint string
|
|
||||||
Insecure bool
|
|
||||||
RoundTripperCount uint
|
|
||||||
credentialsLock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientLock sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// Connect makes connection to vCenter and sets VSphereConnection.Client.
|
|
||||||
// If connection.Client is already set, it obtains the existing user session.
|
|
||||||
// if user session is not valid, connection.Client will be set to the new client.
|
|
||||||
func (connection *VSphereConnection) Connect(ctx context.Context) error {
|
|
||||||
var err error
|
|
||||||
clientLock.Lock()
|
|
||||||
defer clientLock.Unlock()
|
|
||||||
|
|
||||||
if connection.Client == nil {
|
|
||||||
connection.Client, err = connection.NewClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create govmomi client. err: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setVCenterInfoMetric(connection)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m := session.NewManager(connection.Client)
|
|
||||||
userSession, err := m.UserSession(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error while obtaining user session. err: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if userSession != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
klog.Warningf("Creating new client session since the existing session is not valid or not authenticated")
|
|
||||||
|
|
||||||
connection.Client, err = connection.NewClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create govmomi client. err: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signer returns an sts.Signer for use with SAML token auth if connection is configured for such.
|
|
||||||
// Returns nil if username/password auth is configured for the connection.
|
|
||||||
func (connection *VSphereConnection) Signer(ctx context.Context, client *vim25.Client) (*sts.Signer, error) {
|
|
||||||
// TODO: Add separate fields for certificate and private-key.
|
|
||||||
// For now we can leave the config structs and validation as-is and
|
|
||||||
// decide to use LoginByToken if the username value is PEM encoded.
|
|
||||||
b, _ := pem.Decode([]byte(connection.Username))
|
|
||||||
if b == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := tls.X509KeyPair([]byte(connection.Username), []byte(connection.Password))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to load X509 key pair. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens, err := sts.NewClient(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create STS client. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := sts.TokenRequest{
|
|
||||||
Certificate: &cert,
|
|
||||||
Delegatable: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
signer, err := tokens.Issue(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to issue SAML token. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return signer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// login calls SessionManager.LoginByToken if certificate and private key are configured,
|
|
||||||
// otherwise calls SessionManager.Login with user and password.
|
|
||||||
func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Client) error {
|
|
||||||
m := session.NewManager(client)
|
|
||||||
connection.credentialsLock.Lock()
|
|
||||||
defer connection.credentialsLock.Unlock()
|
|
||||||
|
|
||||||
signer, err := connection.Signer(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if signer == nil {
|
|
||||||
klog.V(3).Infof("SessionManager.Login with username %q", connection.Username)
|
|
||||||
return m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password))
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(3).Infof("SessionManager.LoginByToken with certificate %q", connection.Username)
|
|
||||||
|
|
||||||
header := soap.Header{Security: signer}
|
|
||||||
|
|
||||||
return m.LoginByToken(client.WithHeader(ctx, header))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout calls SessionManager.Logout for the given connection.
|
|
||||||
func (connection *VSphereConnection) Logout(ctx context.Context) {
|
|
||||||
clientLock.Lock()
|
|
||||||
c := connection.Client
|
|
||||||
clientLock.Unlock()
|
|
||||||
if c == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m := session.NewManager(c)
|
|
||||||
|
|
||||||
hasActiveSession, err := m.SessionIsActive(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Logout failed: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !hasActiveSession {
|
|
||||||
klog.Errorf("No active session, cannot logout")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := m.Logout(ctx); err != nil {
|
|
||||||
klog.Errorf("Logout failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new govmomi client for the VSphereConnection obj
|
|
||||||
func (connection *VSphereConnection) NewClient(ctx context.Context) (*vim25.Client, error) {
|
|
||||||
url, err := soap.ParseURL(net.JoinHostPort(connection.Hostname, connection.Port))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to parse URL: %s. err: %+v", url, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := soap.NewClient(url, connection.Insecure)
|
|
||||||
|
|
||||||
if ca := connection.CACert; ca != "" {
|
|
||||||
if err := sc.SetRootCAs(ca); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tpHost := connection.Hostname + ":" + connection.Port
|
|
||||||
sc.SetThumbprint(tpHost, connection.Thumbprint)
|
|
||||||
|
|
||||||
client, err := vim25.NewClient(ctx, sc)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create new client. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k8sVersion := version.Get().GitVersion
|
|
||||||
client.UserAgent = fmt.Sprintf("kubernetes-cloudprovider/%s", k8sVersion)
|
|
||||||
|
|
||||||
err = connection.login(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klogV := klog.V(3)
|
|
||||||
if klogV.Enabled() {
|
|
||||||
s, err := session.NewManager(client).UserSession(ctx)
|
|
||||||
if err == nil {
|
|
||||||
klogV.Infof("New session ID for '%s' = %s", s.UserName, s.Key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if connection.RoundTripperCount == 0 {
|
|
||||||
connection.RoundTripperCount = RoundTripperDefaultCount
|
|
||||||
}
|
|
||||||
client.RoundTripper = vim25.Retry(client.RoundTripper, vim25.TemporaryNetworkError(int(connection.RoundTripperCount)))
|
|
||||||
vcNotSupported, err := isvCenterNotSupported(client.ServiceContent.About.Version, client.ServiceContent.About.ApiVersion)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("failed to check if vCenter version:%v and api version: %s is supported or not. Error: %v", client.ServiceContent.About.Version, client.ServiceContent.About.ApiVersion, err)
|
|
||||||
}
|
|
||||||
if vcNotSupported {
|
|
||||||
klog.Warningf("vCenter version (version: %q, api verson: %q) is not supported for CSI Migration. Please consider upgrading vCenter and ESXi servers to 7.0u2 or higher for migrating vSphere volumes to CSI.", client.ServiceContent.About.Version, client.ServiceContent.About.ApiVersion)
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateCredentials updates username and password.
|
|
||||||
// Note: Updated username and password will be used when there is no session active
|
|
||||||
func (connection *VSphereConnection) UpdateCredentials(username string, password string) {
|
|
||||||
connection.credentialsLock.Lock()
|
|
||||||
defer connection.credentialsLock.Unlock()
|
|
||||||
connection.Username = username
|
|
||||||
connection.Password = password
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vclib_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createTestServer(
|
|
||||||
t *testing.T,
|
|
||||||
caCertPath string,
|
|
||||||
serverCertPath string,
|
|
||||||
serverKeyPath string,
|
|
||||||
handler http.HandlerFunc,
|
|
||||||
) (*httptest.Server, string) {
|
|
||||||
caCertPEM, err := ioutil.ReadFile(caCertPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not read ca cert from file")
|
|
||||||
}
|
|
||||||
|
|
||||||
serverCert, err := tls.LoadX509KeyPair(serverCertPath, serverKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not load server cert and server key from files: %#v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok {
|
|
||||||
t.Fatalf("Cannot add CA to CAPool")
|
|
||||||
}
|
|
||||||
|
|
||||||
server := httptest.NewUnstartedServer(http.HandlerFunc(handler))
|
|
||||||
server.TLS = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{
|
|
||||||
serverCert,
|
|
||||||
},
|
|
||||||
RootCAs: certPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate the leaf certificate's fingerprint
|
|
||||||
if len(server.TLS.Certificates) < 1 || len(server.TLS.Certificates[0].Certificate) < 1 {
|
|
||||||
t.Fatal("Expected server.TLS.Certificates not to be empty")
|
|
||||||
}
|
|
||||||
x509LeafCert := server.TLS.Certificates[0].Certificate[0]
|
|
||||||
var tpString string
|
|
||||||
for i, b := range sha1.Sum(x509LeafCert) {
|
|
||||||
if i > 0 {
|
|
||||||
tpString += ":"
|
|
||||||
}
|
|
||||||
tpString += fmt.Sprintf("%02X", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return server, tpString
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithValidCaCert(t *testing.T) {
|
|
||||||
handler, verifyConnectionWasMade := getRequestVerifier(t)
|
|
||||||
|
|
||||||
server, _ := createTestServer(t, "./testdata/ca.pem", "./testdata/server.pem", "./testdata/server.key", handler)
|
|
||||||
server.StartTLS()
|
|
||||||
u := mustParseURL(t, server.URL)
|
|
||||||
|
|
||||||
connection := &vclib.VSphereConnection{
|
|
||||||
Hostname: u.Hostname(),
|
|
||||||
Port: u.Port(),
|
|
||||||
CACert: "./testdata/ca.pem",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignoring error here, because we only care about the TLS connection
|
|
||||||
_, _ = connection.NewClient(context.Background())
|
|
||||||
|
|
||||||
verifyConnectionWasMade()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithVerificationWithWrongThumbprint(t *testing.T) {
|
|
||||||
handler, _ := getRequestVerifier(t)
|
|
||||||
|
|
||||||
server, _ := createTestServer(t, "./testdata/ca.pem", "./testdata/server.pem", "./testdata/server.key", handler)
|
|
||||||
server.StartTLS()
|
|
||||||
u := mustParseURL(t, server.URL)
|
|
||||||
|
|
||||||
connection := &vclib.VSphereConnection{
|
|
||||||
Hostname: u.Hostname(),
|
|
||||||
Port: u.Port(),
|
|
||||||
Thumbprint: "obviously wrong",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := connection.NewClient(context.Background())
|
|
||||||
|
|
||||||
if msg := err.Error(); !strings.Contains(msg, "thumbprint does not match") {
|
|
||||||
t.Fatalf("Expected wrong thumbprint error, got '%s'", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithVerificationWithoutCaCertOrThumbprint(t *testing.T) {
|
|
||||||
handler, _ := getRequestVerifier(t)
|
|
||||||
|
|
||||||
server, _ := createTestServer(t, "./testdata/ca.pem", "./testdata/server.pem", "./testdata/server.key", handler)
|
|
||||||
server.StartTLS()
|
|
||||||
u := mustParseURL(t, server.URL)
|
|
||||||
|
|
||||||
connection := &vclib.VSphereConnection{
|
|
||||||
Hostname: u.Hostname(),
|
|
||||||
Port: u.Port(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := connection.NewClient(context.Background())
|
|
||||||
|
|
||||||
verifyWrappedX509UnkownAuthorityErr(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithValidThumbprint(t *testing.T) {
|
|
||||||
handler, verifyConnectionWasMade := getRequestVerifier(t)
|
|
||||||
|
|
||||||
server, thumbprint :=
|
|
||||||
createTestServer(t, "./testdata/ca.pem", "./testdata/server.pem", "./testdata/server.key", handler)
|
|
||||||
server.StartTLS()
|
|
||||||
u := mustParseURL(t, server.URL)
|
|
||||||
|
|
||||||
connection := &vclib.VSphereConnection{
|
|
||||||
Hostname: u.Hostname(),
|
|
||||||
Port: u.Port(),
|
|
||||||
Thumbprint: thumbprint,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignoring error here, because we only care about the TLS connection
|
|
||||||
_, _ = connection.NewClient(context.Background())
|
|
||||||
|
|
||||||
verifyConnectionWasMade()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithInvalidCaCertPath(t *testing.T) {
|
|
||||||
connection := &vclib.VSphereConnection{
|
|
||||||
Hostname: "should-not-matter",
|
|
||||||
Port: "27015", // doesn't matter, but has to be a valid port
|
|
||||||
CACert: "invalid-path",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := connection.NewClient(context.Background())
|
|
||||||
if _, ok := err.(*os.PathError); !ok {
|
|
||||||
t.Fatalf("Expected an os.PathError, got: '%s' (%#v)", err.Error(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidCaCert(t *testing.T) {
|
|
||||||
connection := &vclib.VSphereConnection{
|
|
||||||
Hostname: "should-not-matter",
|
|
||||||
Port: "27015", // doesn't matter, but has to be a valid port
|
|
||||||
CACert: "./testdata/invalid.pem",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := connection.NewClient(context.Background())
|
|
||||||
|
|
||||||
if msg := err.Error(); !strings.Contains(msg, "invalid certificate") {
|
|
||||||
t.Fatalf("Expected invalid certificate error, got '%s'", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyWrappedX509UnkownAuthorityErr(t *testing.T, err error) {
|
|
||||||
urlErr, ok := err.(*url.Error)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected to receive an url.Error, got '%s' (%#v)", err.Error(), err)
|
|
||||||
}
|
|
||||||
var x509err x509.UnknownAuthorityError
|
|
||||||
if !errors.As(urlErr.Err, &x509err) {
|
|
||||||
t.Fatalf("Expected to receive a wrapped x509.UnknownAuthorityError, got: '%s' (%#v)", urlErr.Err.Error(), urlErr.Err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRequestVerifier(t *testing.T) (http.HandlerFunc, func()) {
|
|
||||||
gotRequest := false
|
|
||||||
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
gotRequest = true
|
|
||||||
}
|
|
||||||
|
|
||||||
checker := func() {
|
|
||||||
if !gotRequest {
|
|
||||||
t.Fatalf("Never saw a request, maybe TLS connection could not be established?")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler, checker
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustParseURL(t *testing.T, i string) *url.URL {
|
|
||||||
u, err := url.Parse(i)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Cannot parse URL: %v", err)
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
// Volume Constants
|
|
||||||
const (
|
|
||||||
ThinDiskType = "thin"
|
|
||||||
PreallocatedDiskType = "preallocated"
|
|
||||||
EagerZeroedThickDiskType = "eagerZeroedThick"
|
|
||||||
ZeroedThickDiskType = "zeroedThick"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Controller Constants
|
|
||||||
const (
|
|
||||||
SCSIControllerLimit = 4
|
|
||||||
SCSIControllerDeviceLimit = 15
|
|
||||||
SCSIDeviceSlots = 16
|
|
||||||
SCSIReservedSlot = 7
|
|
||||||
|
|
||||||
SCSIControllerType = "scsi"
|
|
||||||
LSILogicControllerType = "lsiLogic"
|
|
||||||
BusLogicControllerType = "busLogic"
|
|
||||||
LSILogicSASControllerType = "lsiLogic-sas"
|
|
||||||
PVSCSIControllerType = "pvscsi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Other Constants
|
|
||||||
const (
|
|
||||||
LogLevel = 4
|
|
||||||
DatastoreProperty = "datastore"
|
|
||||||
ResourcePoolProperty = "resourcePool"
|
|
||||||
DatastoreInfoProperty = "info"
|
|
||||||
VirtualMachineType = "VirtualMachine"
|
|
||||||
RoundTripperDefaultCount = 3
|
|
||||||
VSANDatastoreType = "vsan"
|
|
||||||
DummyVMPrefixName = "vsphere-k8s"
|
|
||||||
ActivePowerState = "poweredOn"
|
|
||||||
DatacenterType = "Datacenter"
|
|
||||||
ClusterComputeResourceType = "ClusterComputeResource"
|
|
||||||
HostSystemType = "HostSystem"
|
|
||||||
NameProperty = "name"
|
|
||||||
MinvCenterVersion = "7.0.2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test Constants
|
|
||||||
const (
|
|
||||||
TestDefaultDatacenter = "DC0"
|
|
||||||
TestDefaultDatastore = "LocalDS_0"
|
|
||||||
TestDefaultNetwork = "VM Network"
|
|
||||||
testNameNotFound = "enoent"
|
|
||||||
)
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
// Error Messages
|
|
||||||
const (
|
|
||||||
FileAlreadyExistErrMsg = "File requested already exist"
|
|
||||||
NoDiskUUIDFoundErrMsg = "No disk UUID found"
|
|
||||||
NoDevicesFoundErrMsg = "No devices found"
|
|
||||||
DiskNotFoundErrMsg = "No vSphere disk ID found"
|
|
||||||
NoVMFoundErrMsg = "No VM found"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error constants
|
|
||||||
var (
|
|
||||||
ErrFileAlreadyExist = errors.New(FileAlreadyExistErrMsg)
|
|
||||||
ErrNoDiskUUIDFound = errors.New(NoDiskUUIDFoundErrMsg)
|
|
||||||
ErrNoDevicesFound = errors.New(NoDevicesFoundErrMsg)
|
|
||||||
ErrNoDiskIDFound = errors.New(DiskNotFoundErrMsg)
|
|
||||||
ErrNoVMFound = errors.New(NoVMFoundErrMsg)
|
|
||||||
)
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/find"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/property"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Datacenter extends the govmomi Datacenter object
|
|
||||||
type Datacenter struct {
|
|
||||||
*object.Datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatacenter returns the DataCenter Object for the given datacenterPath
|
|
||||||
// If datacenter is located in a folder, include full path to datacenter else just provide the datacenter name
|
|
||||||
func GetDatacenter(ctx context.Context, connection *VSphereConnection, datacenterPath string) (*Datacenter, error) {
|
|
||||||
finder := find.NewFinder(connection.Client, false)
|
|
||||||
datacenter, err := finder.Datacenter(ctx, datacenterPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to find the datacenter: %s. err: %+v", datacenterPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dc := Datacenter{datacenter}
|
|
||||||
return &dc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllDatacenter returns all the DataCenter Objects
|
|
||||||
func GetAllDatacenter(ctx context.Context, connection *VSphereConnection) ([]*Datacenter, error) {
|
|
||||||
var dc []*Datacenter
|
|
||||||
finder := find.NewFinder(connection.Client, false)
|
|
||||||
datacenters, err := finder.DatacenterList(ctx, "*")
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to find the datacenter. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, datacenter := range datacenters {
|
|
||||||
dc = append(dc, &(Datacenter{datacenter}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return dc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMByUUID gets the VM object from the given vmUUID
|
|
||||||
func (dc *Datacenter) GetVMByUUID(ctx context.Context, vmUUID string) (*VirtualMachine, error) {
|
|
||||||
s := object.NewSearchIndex(dc.Client())
|
|
||||||
vmUUID = strings.ToLower(strings.TrimSpace(vmUUID))
|
|
||||||
svm, err := s.FindByUuid(ctx, dc.Datacenter, vmUUID, true, nil)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to find VM by UUID. VM UUID: %s, err: %+v", vmUUID, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if svm == nil {
|
|
||||||
klog.Errorf("Unable to find VM by UUID. VM UUID: %s", vmUUID)
|
|
||||||
return nil, ErrNoVMFound
|
|
||||||
}
|
|
||||||
virtualMachine := VirtualMachine{object.NewVirtualMachine(dc.Client(), svm.Reference()), dc}
|
|
||||||
return &virtualMachine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHostByVMUUID gets the host object from the given vmUUID
|
|
||||||
func (dc *Datacenter) GetHostByVMUUID(ctx context.Context, vmUUID string) (*types.ManagedObjectReference, error) {
|
|
||||||
virtualMachine, err := dc.GetVMByUUID(ctx, vmUUID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var vmMo mo.VirtualMachine
|
|
||||||
pc := property.DefaultCollector(virtualMachine.Client())
|
|
||||||
err = pc.RetrieveOne(ctx, virtualMachine.Reference(), []string{"summary.runtime.host"}, &vmMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve VM runtime host, err: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
host := vmMo.Summary.Runtime.Host
|
|
||||||
klog.Infof("%s host is %s", virtualMachine.Reference(), host)
|
|
||||||
return host, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMByPath gets the VM object from the given vmPath
|
|
||||||
// vmPath should be the full path to VM and not just the name
|
|
||||||
func (dc *Datacenter) GetVMByPath(ctx context.Context, vmPath string) (*VirtualMachine, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
vm, err := finder.VirtualMachine(ctx, vmPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to find VM by Path. VM Path: %s, err: %+v", vmPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
virtualMachine := VirtualMachine{vm, dc}
|
|
||||||
return &virtualMachine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllDatastores gets the datastore URL to DatastoreInfo map for all the datastores in
|
|
||||||
// the datacenter.
|
|
||||||
func (dc *Datacenter) GetAllDatastores(ctx context.Context) (map[string]*DatastoreInfo, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
datastores, err := finder.DatastoreList(ctx, "*")
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get all the datastores. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var dsList []types.ManagedObjectReference
|
|
||||||
for _, ds := range datastores {
|
|
||||||
dsList = append(dsList, ds.Reference())
|
|
||||||
}
|
|
||||||
|
|
||||||
var dsMoList []mo.Datastore
|
|
||||||
pc := property.DefaultCollector(dc.Client())
|
|
||||||
properties := []string{DatastoreInfoProperty}
|
|
||||||
err = pc.Retrieve(ctx, dsList, properties, &dsMoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Datastore managed objects from datastore objects."+
|
|
||||||
" dsObjList: %+v, properties: %+v, err: %v", dsList, properties, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dsURLInfoMap := make(map[string]*DatastoreInfo)
|
|
||||||
for _, dsMo := range dsMoList {
|
|
||||||
dsURLInfoMap[dsMo.Info.GetDatastoreInfo().Url] = &DatastoreInfo{
|
|
||||||
&Datastore{object.NewDatastore(dc.Client(), dsMo.Reference()),
|
|
||||||
dc},
|
|
||||||
dsMo.Info.GetDatastoreInfo()}
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("dsURLInfoMap : %+v", dsURLInfoMap)
|
|
||||||
return dsURLInfoMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllHosts returns all the host objects in this datacenter of VC
|
|
||||||
func (dc *Datacenter) GetAllHosts(ctx context.Context) ([]types.ManagedObjectReference, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
hostSystems, err := finder.HostSystemList(ctx, "*")
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get all hostSystems. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hostMors []types.ManagedObjectReference
|
|
||||||
for _, hs := range hostSystems {
|
|
||||||
hostMors = append(hostMors, hs.Reference())
|
|
||||||
}
|
|
||||||
return hostMors, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreByPath gets the Datastore object from the given vmDiskPath
|
|
||||||
func (dc *Datacenter) GetDatastoreByPath(ctx context.Context, vmDiskPath string) (*Datastore, error) {
|
|
||||||
datastorePathObj := new(object.DatastorePath)
|
|
||||||
isSuccess := datastorePathObj.FromString(vmDiskPath)
|
|
||||||
if !isSuccess {
|
|
||||||
klog.Errorf("Failed to parse vmDiskPath: %s", vmDiskPath)
|
|
||||||
return nil, errors.New("Failed to parse vmDiskPath")
|
|
||||||
}
|
|
||||||
|
|
||||||
return dc.GetDatastoreByName(ctx, datastorePathObj.Datastore)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreByName gets the Datastore object for the given datastore name
|
|
||||||
func (dc *Datacenter) GetDatastoreByName(ctx context.Context, name string) (*Datastore, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
ds, err := finder.Datastore(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed while searching for datastore: %s. err: %+v", name, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datastore := Datastore{ds, dc}
|
|
||||||
return &datastore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreInfoByName gets the Datastore object for the given datastore name
|
|
||||||
func (dc *Datacenter) GetDatastoreInfoByName(ctx context.Context, name string) (*DatastoreInfo, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
ds, err := finder.Datastore(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed while searching for datastore: %s. err: %+v", name, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datastore := Datastore{ds, dc}
|
|
||||||
var dsMo mo.Datastore
|
|
||||||
pc := property.DefaultCollector(dc.Client())
|
|
||||||
properties := []string{DatastoreInfoProperty}
|
|
||||||
err = pc.RetrieveOne(ctx, ds.Reference(), properties, &dsMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Datastore managed objects from datastore reference."+
|
|
||||||
" dsRef: %+v, err: %+v", ds.Reference(), err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("Result dsMo: %+v", dsMo)
|
|
||||||
return &DatastoreInfo{Datastore: &datastore, Info: dsMo.Info.GetDatastoreInfo()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResourcePool gets the resource pool for the given path
|
|
||||||
func (dc *Datacenter) GetResourcePool(ctx context.Context, resourcePoolPath string) (*object.ResourcePool, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
var resourcePool *object.ResourcePool
|
|
||||||
var err error
|
|
||||||
resourcePool, err = finder.ResourcePoolOrDefault(ctx, resourcePoolPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get the ResourcePool for path '%s'. err: %+v", resourcePoolPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resourcePool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFolderByPath gets the Folder Object from the given folder path
|
|
||||||
// folderPath should be the full path to folder
|
|
||||||
func (dc *Datacenter) GetFolderByPath(ctx context.Context, folderPath string) (*Folder, error) {
|
|
||||||
finder := getFinder(dc)
|
|
||||||
vmFolder, err := finder.Folder(ctx, folderPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get the folder reference for %s. err: %+v", folderPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
folder := Folder{vmFolder, dc}
|
|
||||||
return &folder, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMMoList gets the VM Managed Objects with the given properties from the VM object
|
|
||||||
func (dc *Datacenter) GetVMMoList(ctx context.Context, vmObjList []*VirtualMachine, properties []string) ([]mo.VirtualMachine, error) {
|
|
||||||
var vmMoList []mo.VirtualMachine
|
|
||||||
var vmRefs []types.ManagedObjectReference
|
|
||||||
if len(vmObjList) < 1 {
|
|
||||||
klog.Errorf("VirtualMachine Object list is empty")
|
|
||||||
return nil, fmt.Errorf("VirtualMachine Object list is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vmObj := range vmObjList {
|
|
||||||
vmRefs = append(vmRefs, vmObj.Reference())
|
|
||||||
}
|
|
||||||
pc := property.DefaultCollector(dc.Client())
|
|
||||||
err := pc.Retrieve(ctx, vmRefs, properties, &vmMoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get VM managed objects from VM objects. vmObjList: %+v, properties: %+v, err: %v", vmObjList, properties, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return vmMoList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVirtualDiskPage83Data gets the virtual disk UUID by diskPath
|
|
||||||
func (dc *Datacenter) GetVirtualDiskPage83Data(ctx context.Context, diskPath string) (string, error) {
|
|
||||||
if len(diskPath) > 0 && filepath.Ext(diskPath) != ".vmdk" {
|
|
||||||
diskPath += ".vmdk"
|
|
||||||
}
|
|
||||||
vdm := object.NewVirtualDiskManager(dc.Client())
|
|
||||||
// Returns uuid of vmdk virtual disk
|
|
||||||
diskUUID, err := vdm.QueryVirtualDiskUuid(ctx, diskPath, dc.Datacenter)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Warningf("QueryVirtualDiskUuid failed for diskPath: %q. err: %+v", diskPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
diskUUID = formatVirtualDiskUUID(diskUUID)
|
|
||||||
return diskUUID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreMoList gets the Datastore Managed Objects with the given properties from the datastore objects
|
|
||||||
func (dc *Datacenter) GetDatastoreMoList(ctx context.Context, dsObjList []*Datastore, properties []string) ([]mo.Datastore, error) {
|
|
||||||
var dsMoList []mo.Datastore
|
|
||||||
var dsRefs []types.ManagedObjectReference
|
|
||||||
if len(dsObjList) < 1 {
|
|
||||||
klog.Errorf("Datastore Object list is empty")
|
|
||||||
return nil, fmt.Errorf("Datastore Object list is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dsObj := range dsObjList {
|
|
||||||
dsRefs = append(dsRefs, dsObj.Reference())
|
|
||||||
}
|
|
||||||
pc := property.DefaultCollector(dc.Client())
|
|
||||||
err := pc.Retrieve(ctx, dsRefs, properties, &dsMoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Datastore managed objects from datastore objects. dsObjList: %+v, properties: %+v, err: %v", dsObjList, properties, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dsMoList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDisksAttached checks if the disk is attached to node.
|
|
||||||
// This is done by comparing the volume path with the backing.FilePath on the VM Virtual disk devices.
|
|
||||||
func (dc *Datacenter) CheckDisksAttached(ctx context.Context, nodeVolumes map[string][]string) (map[string]map[string]bool, error) {
|
|
||||||
attached := make(map[string]map[string]bool)
|
|
||||||
var vmList []*VirtualMachine
|
|
||||||
for nodeName, volPaths := range nodeVolumes {
|
|
||||||
for _, volPath := range volPaths {
|
|
||||||
setNodeVolumeMap(attached, volPath, nodeName, false)
|
|
||||||
}
|
|
||||||
vm, err := dc.GetVMByPath(ctx, nodeName)
|
|
||||||
if err != nil {
|
|
||||||
if IsNotFound(err) {
|
|
||||||
klog.Warningf("Node %q does not exist, vSphere CP will assume disks %v are not attached to it.", nodeName, volPaths)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vmList = append(vmList, vm)
|
|
||||||
}
|
|
||||||
if len(vmList) == 0 {
|
|
||||||
klog.V(2).Infof("vSphere CP will assume no disks are attached to any node.")
|
|
||||||
return attached, nil
|
|
||||||
}
|
|
||||||
vmMoList, err := dc.GetVMMoList(ctx, vmList, []string{"config.hardware.device", "name"})
|
|
||||||
if err != nil {
|
|
||||||
// When there is an error fetching instance information
|
|
||||||
// it is safer to return nil and let volume information not be touched.
|
|
||||||
klog.Errorf("Failed to get VM Managed object for nodes: %+v. err: +%v", vmList, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vmMo := range vmMoList {
|
|
||||||
if vmMo.Config == nil {
|
|
||||||
klog.Errorf("Config is not available for VM: %q", vmMo.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for nodeName, volPaths := range nodeVolumes {
|
|
||||||
if nodeName == vmMo.Name {
|
|
||||||
verifyVolumePathsForVM(vmMo, volPaths, attached)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attached, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyVolumePathsForVM verifies if the volume paths (volPaths) are attached to VM.
|
|
||||||
func verifyVolumePathsForVM(vmMo mo.VirtualMachine, volPaths []string, nodeVolumeMap map[string]map[string]bool) {
|
|
||||||
// Verify if the volume paths are present on the VM backing virtual disk devices
|
|
||||||
for _, volPath := range volPaths {
|
|
||||||
vmDevices := object.VirtualDeviceList(vmMo.Config.Hardware.Device)
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
if vmDevices.TypeName(device) == "VirtualDisk" {
|
|
||||||
virtualDevice := device.GetVirtualDevice()
|
|
||||||
if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
|
||||||
if backing.FileName == volPath {
|
|
||||||
setNodeVolumeMap(nodeVolumeMap, volPath, vmMo.Name, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setNodeVolumeMap(
|
|
||||||
nodeVolumeMap map[string]map[string]bool,
|
|
||||||
volumePath string,
|
|
||||||
nodeName string,
|
|
||||||
check bool) {
|
|
||||||
volumeMap := nodeVolumeMap[nodeName]
|
|
||||||
if volumeMap == nil {
|
|
||||||
volumeMap = make(map[string]bool)
|
|
||||||
nodeVolumeMap[nodeName] = volumeMap
|
|
||||||
}
|
|
||||||
volumeMap[volumePath] = check
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/simulator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDatacenter(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// vCenter model + initial set of objects (cluster, hosts, VMs, network, datastore, etc)
|
|
||||||
model := simulator.VPX()
|
|
||||||
|
|
||||||
defer model.Remove()
|
|
||||||
err := model.Create()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := model.Service.NewServer()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
avm := simulator.Map.Any(VirtualMachineType).(*simulator.VirtualMachine)
|
|
||||||
|
|
||||||
c, err := govmomi.NewClient(ctx, s.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vc := &VSphereConnection{Client: c.Client}
|
|
||||||
|
|
||||||
_, err = GetDatacenter(ctx, vc, testNameNotFound)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVMByUUID(ctx, testNameNotFound)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVMByUUID(ctx, avm.Summary.Config.Uuid)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVMByPath(ctx, testNameNotFound)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
vm, err := dc.GetVMByPath(ctx, TestDefaultDatacenter+"/vm/"+avm.Name)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetDatastoreByPath(ctx, testNameNotFound) // invalid format
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidPath := object.DatastorePath{
|
|
||||||
Datastore: testNameNotFound,
|
|
||||||
Path: testNameNotFound,
|
|
||||||
}
|
|
||||||
_, err = dc.GetDatastoreByPath(ctx, invalidPath.String())
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetDatastoreByPath(ctx, avm.Summary.Config.VmPathName)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetDatastoreByName(ctx, testNameNotFound)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
ds, err := dc.GetDatastoreByName(ctx, TestDefaultDatastore)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetFolderByPath(ctx, testNameNotFound)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetFolderByPath(ctx, TestDefaultDatacenter+"/vm")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVMMoList(ctx, nil, nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVMMoList(ctx, []*VirtualMachine{vm}, []string{testNameNotFound}) // invalid property
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVMMoList(ctx, []*VirtualMachine{vm}, []string{"summary"})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
diskPath := ds.Path(avm.Name + "/disk1.vmdk")
|
|
||||||
|
|
||||||
_, err = dc.GetVirtualDiskPage83Data(ctx, diskPath+testNameNotFound)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetVirtualDiskPage83Data(ctx, diskPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetDatastoreMoList(ctx, nil, nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetDatastoreMoList(ctx, []*Datastore{ds}, []string{testNameNotFound}) // invalid property
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = dc.GetDatastoreMoList(ctx, []*Datastore{ds}, []string{DatastoreInfoProperty})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeVolumes := map[string][]string{
|
|
||||||
avm.Name: {testNameNotFound, diskPath},
|
|
||||||
}
|
|
||||||
|
|
||||||
attached, err := dc.CheckDisksAttached(ctx, nodeVolumes)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if attached[avm.Name][testNameNotFound] {
|
|
||||||
t.Error("should not be attached")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !attached[avm.Name][diskPath] {
|
|
||||||
t.Errorf("%s should be attached", diskPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/property"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Datastore extends the govmomi Datastore object
|
|
||||||
type Datastore struct {
|
|
||||||
*object.Datastore
|
|
||||||
Datacenter *Datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatastoreInfo is a structure to store the Datastore and it's Info.
|
|
||||||
type DatastoreInfo struct {
|
|
||||||
*Datastore
|
|
||||||
Info *types.DatastoreInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (di DatastoreInfo) String() string {
|
|
||||||
return fmt.Sprintf("Datastore: %+v, datastore URL: %s", di.Datastore, di.Info.Url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDirectory creates the directory at location specified by directoryPath.
|
|
||||||
// If the intermediate level folders do not exist, and the parameter createParents is true, all the non-existent folders are created.
|
|
||||||
// directoryPath must be in the format "[vsanDatastore] kubevols"
|
|
||||||
func (ds *Datastore) CreateDirectory(ctx context.Context, directoryPath string, createParents bool) error {
|
|
||||||
fileManager := object.NewFileManager(ds.Client())
|
|
||||||
err := fileManager.MakeDirectory(ctx, directoryPath, ds.Datacenter.Datacenter, createParents)
|
|
||||||
if err != nil {
|
|
||||||
if soap.IsSoapFault(err) {
|
|
||||||
soapFault := soap.ToSoapFault(err)
|
|
||||||
if _, ok := soapFault.VimFault().(types.FileAlreadyExists); ok {
|
|
||||||
return ErrFileAlreadyExist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
klog.V(LogLevel).Infof("Created dir with path as %+q", directoryPath)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetType returns the type of datastore
|
|
||||||
func (ds *Datastore) GetType(ctx context.Context) (string, error) {
|
|
||||||
var dsMo mo.Datastore
|
|
||||||
pc := property.DefaultCollector(ds.Client())
|
|
||||||
err := pc.RetrieveOne(ctx, ds.Datastore.Reference(), []string{"summary"}, &dsMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve datastore summary property. err: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return dsMo.Summary.Type, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCompatibleWithStoragePolicy returns true if datastore is compatible with given storage policy else return false
|
|
||||||
// for not compatible datastore, fault message is also returned
|
|
||||||
func (ds *Datastore) IsCompatibleWithStoragePolicy(ctx context.Context, storagePolicyID string) (bool, string, error) {
|
|
||||||
pbmClient, err := NewPbmClient(ctx, ds.Client())
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get new PbmClient Object. err: %v", err)
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
return pbmClient.IsDatastoreCompatible(ctx, storagePolicyID, ds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreHostMounts gets the host names mounted on given datastore
|
|
||||||
func (ds *Datastore) GetDatastoreHostMounts(ctx context.Context) ([]types.ManagedObjectReference, error) {
|
|
||||||
var dsMo mo.Datastore
|
|
||||||
pc := property.DefaultCollector(ds.Client())
|
|
||||||
err := pc.RetrieveOne(ctx, ds.Datastore.Reference(), []string{"host"}, &dsMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve datastore host mount property. err: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hosts := make([]types.ManagedObjectReference, len(dsMo.Host))
|
|
||||||
for i, dsHostMount := range dsMo.Host {
|
|
||||||
hosts[i] = dsHostMount.Key
|
|
||||||
}
|
|
||||||
return hosts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists returns whether the given file exists in this datastore
|
|
||||||
func (ds *Datastore) Exists(ctx context.Context, file string) bool {
|
|
||||||
_, err := ds.Datastore.Stat(ctx, file)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/simulator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDatastore(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// vCenter model + initial set of objects (cluster, hosts, VMs, network, datastore, etc)
|
|
||||||
model := simulator.VPX()
|
|
||||||
|
|
||||||
defer model.Remove()
|
|
||||||
err := model.Create()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := model.Service.NewServer()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
c, err := govmomi.NewClient(ctx, s.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vc := &VSphereConnection{Client: c.Client}
|
|
||||||
|
|
||||||
dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
all, err := dc.GetAllDatastores(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range all {
|
|
||||||
ds := info.Datastore
|
|
||||||
kind, cerr := ds.GetType(ctx)
|
|
||||||
if cerr != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if kind == "" {
|
|
||||||
t.Error("empty Datastore type")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := object.DatastorePath{
|
|
||||||
Datastore: info.Info.Name,
|
|
||||||
Path: "kubevols",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: test Datastore.IsCompatibleWithStoragePolicy (vcsim needs PBM support)
|
|
||||||
|
|
||||||
for _, fail := range []bool{false, true} {
|
|
||||||
cerr = ds.CreateDirectory(ctx, dir.String(), false)
|
|
||||||
if fail {
|
|
||||||
if cerr != ErrFileAlreadyExist {
|
|
||||||
t.Errorf("expected %s, got: %s", ErrFileAlreadyExist, cerr)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if cerr != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 diskmanagers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// virtualDiskManager implements VirtualDiskProvider Interface for creating and deleting volume using VirtualDiskManager
|
|
||||||
type virtualDiskManager struct {
|
|
||||||
diskPath string
|
|
||||||
volumeOptions *vclib.VolumeOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create implements Disk's Create interface
|
|
||||||
// Contains implementation of virtualDiskManager based Provisioning
|
|
||||||
func (diskManager virtualDiskManager) Create(ctx context.Context, datastore *vclib.Datastore) (canonicalDiskPath string, err error) {
|
|
||||||
if diskManager.volumeOptions.SCSIControllerType == "" {
|
|
||||||
diskManager.volumeOptions.SCSIControllerType = vclib.LSILogicControllerType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing VMDK before attempting create. Because a name collision
|
|
||||||
// is unlikely, "VMDK already exists" is likely from a previous attempt to
|
|
||||||
// create this volume.
|
|
||||||
if dsPath := vclib.GetPathFromVMDiskPath(diskManager.diskPath); datastore.Exists(ctx, dsPath) {
|
|
||||||
klog.V(2).Infof("Create: VirtualDisk already exists, returning success. Name=%q", diskManager.diskPath)
|
|
||||||
return diskManager.diskPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create specification for new virtual disk
|
|
||||||
diskFormat := vclib.DiskFormatValidType[diskManager.volumeOptions.DiskFormat]
|
|
||||||
vmDiskSpec := &types.FileBackedVirtualDiskSpec{
|
|
||||||
VirtualDiskSpec: types.VirtualDiskSpec{
|
|
||||||
AdapterType: diskManager.volumeOptions.SCSIControllerType,
|
|
||||||
DiskType: diskFormat,
|
|
||||||
},
|
|
||||||
CapacityKb: int64(diskManager.volumeOptions.CapacityKB),
|
|
||||||
}
|
|
||||||
|
|
||||||
vdm := object.NewVirtualDiskManager(datastore.Client())
|
|
||||||
requestTime := time.Now()
|
|
||||||
// Create virtual disk
|
|
||||||
task, err := vdm.CreateVirtualDisk(ctx, diskManager.diskPath, datastore.Datacenter.Datacenter, vmDiskSpec)
|
|
||||||
if err != nil {
|
|
||||||
vclib.RecordvSphereMetric(vclib.APICreateVolume, requestTime, err)
|
|
||||||
klog.Errorf("Failed to create virtual disk: %s. err: %+v", diskManager.diskPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
taskInfo, err := task.WaitForResult(ctx, nil)
|
|
||||||
vclib.RecordvSphereMetric(vclib.APICreateVolume, requestTime, err)
|
|
||||||
if err != nil {
|
|
||||||
if isAlreadyExists(diskManager.diskPath, err) {
|
|
||||||
// The disk already exists, log info message and return success
|
|
||||||
klog.V(vclib.LogLevel).Infof("File: %v already exists", diskManager.diskPath)
|
|
||||||
return diskManager.diskPath, nil
|
|
||||||
}
|
|
||||||
klog.Errorf("Failed to complete virtual disk creation: %s. err: %+v", diskManager.diskPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
canonicalDiskPath = taskInfo.Result.(string)
|
|
||||||
return canonicalDiskPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete implements Disk's Delete interface
|
|
||||||
func (diskManager virtualDiskManager) Delete(ctx context.Context, datacenter *vclib.Datacenter) error {
|
|
||||||
// Create a virtual disk manager
|
|
||||||
virtualDiskManager := object.NewVirtualDiskManager(datacenter.Client())
|
|
||||||
diskPath := vclib.RemoveStorageClusterORFolderNameFromVDiskPath(diskManager.diskPath)
|
|
||||||
requestTime := time.Now()
|
|
||||||
// Delete virtual disk
|
|
||||||
task, err := virtualDiskManager.DeleteVirtualDisk(ctx, diskPath, datacenter.Datacenter)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to delete virtual disk. err: %v", err)
|
|
||||||
vclib.RecordvSphereMetric(vclib.APIDeleteVolume, requestTime, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = task.Wait(ctx)
|
|
||||||
vclib.RecordvSphereMetric(vclib.APIDeleteVolume, requestTime, err)
|
|
||||||
if err != nil && !types.IsFileNotFound(err) {
|
|
||||||
klog.Errorf("Failed to delete virtual disk. err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 diskmanagers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VirtualDisk is for the Disk Management
|
|
||||||
type VirtualDisk struct {
|
|
||||||
DiskPath string
|
|
||||||
VolumeOptions *vclib.VolumeOptions
|
|
||||||
VMOptions *vclib.VMOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// VirtualDisk Operations Const
|
|
||||||
const (
|
|
||||||
VirtualDiskCreateOperation = "Create"
|
|
||||||
VirtualDiskDeleteOperation = "Delete"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VirtualDiskProvider defines interfaces for creating disk
|
|
||||||
type VirtualDiskProvider interface {
|
|
||||||
Create(ctx context.Context, datastore *vclib.Datastore) (string, error)
|
|
||||||
Delete(ctx context.Context, datacenter *vclib.Datacenter) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDiskManager returns vmDiskManager or vdmDiskManager based on given volumeoptions
|
|
||||||
func getDiskManager(disk *VirtualDisk, diskOperation string) VirtualDiskProvider {
|
|
||||||
var diskProvider VirtualDiskProvider
|
|
||||||
switch diskOperation {
|
|
||||||
case VirtualDiskDeleteOperation:
|
|
||||||
diskProvider = virtualDiskManager{disk.DiskPath, disk.VolumeOptions}
|
|
||||||
case VirtualDiskCreateOperation:
|
|
||||||
if disk.VolumeOptions.StoragePolicyName != "" || disk.VolumeOptions.VSANStorageProfileData != "" || disk.VolumeOptions.StoragePolicyID != "" {
|
|
||||||
diskProvider = vmDiskManager{disk.DiskPath, disk.VolumeOptions, disk.VMOptions}
|
|
||||||
} else {
|
|
||||||
diskProvider = virtualDiskManager{disk.DiskPath, disk.VolumeOptions}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diskProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create gets appropriate disk manager and calls respective create method
|
|
||||||
func (virtualDisk *VirtualDisk) Create(ctx context.Context, datastore *vclib.Datastore) (string, error) {
|
|
||||||
if virtualDisk.VolumeOptions.DiskFormat == "" {
|
|
||||||
virtualDisk.VolumeOptions.DiskFormat = vclib.ThinDiskType
|
|
||||||
}
|
|
||||||
if err := virtualDisk.VolumeOptions.VerifyVolumeOptions(); err != nil {
|
|
||||||
klog.Errorf("VolumeOptions verification failed: %s (options: %+v)", err, virtualDisk.VolumeOptions)
|
|
||||||
return "", fmt.Errorf("validation of parameters failed: %s", err)
|
|
||||||
}
|
|
||||||
if virtualDisk.VolumeOptions.StoragePolicyID != "" && virtualDisk.VolumeOptions.StoragePolicyName != "" {
|
|
||||||
return "", fmt.Errorf("Storage Policy ID and Storage Policy Name both set, Please set only one parameter")
|
|
||||||
}
|
|
||||||
return getDiskManager(virtualDisk, VirtualDiskCreateOperation).Create(ctx, datastore)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete gets appropriate disk manager and calls respective delete method
|
|
||||||
func (virtualDisk *VirtualDisk) Delete(ctx context.Context, datacenter *vclib.Datacenter) error {
|
|
||||||
return getDiskManager(virtualDisk, VirtualDiskDeleteOperation).Delete(ctx, datacenter)
|
|
||||||
}
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 diskmanagers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"hash/fnv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// vmDiskManager implements VirtualDiskProvider interface for creating volume using Virtual Machine Reconfigure approach
|
|
||||||
type vmDiskManager struct {
|
|
||||||
diskPath string
|
|
||||||
volumeOptions *vclib.VolumeOptions
|
|
||||||
vmOptions *vclib.VMOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create implements Disk's Create interface
|
|
||||||
// Contains implementation of VM based Provisioning to provision disk with SPBM Policy or VSANStorageProfileData
|
|
||||||
func (vmdisk vmDiskManager) Create(ctx context.Context, datastore *vclib.Datastore) (canonicalDiskPath string, err error) {
|
|
||||||
if vmdisk.volumeOptions.SCSIControllerType == "" {
|
|
||||||
vmdisk.volumeOptions.SCSIControllerType = vclib.PVSCSIControllerType
|
|
||||||
}
|
|
||||||
pbmClient, err := vclib.NewPbmClient(ctx, datastore.Client())
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("error occurred while creating new pbmClient, err: %+v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if vmdisk.volumeOptions.StoragePolicyID == "" && vmdisk.volumeOptions.StoragePolicyName != "" {
|
|
||||||
vmdisk.volumeOptions.StoragePolicyID, err = pbmClient.ProfileIDByName(ctx, vmdisk.volumeOptions.StoragePolicyName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("error occurred while getting Profile Id from Profile Name: %s, err: %+v", vmdisk.volumeOptions.StoragePolicyName, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vmdisk.volumeOptions.StoragePolicyID != "" {
|
|
||||||
compatible, faultMessage, err := datastore.IsCompatibleWithStoragePolicy(ctx, vmdisk.volumeOptions.StoragePolicyID)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("error occurred while checking datastore compatibility with storage policy id: %s, err: %+v", vmdisk.volumeOptions.StoragePolicyID, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !compatible {
|
|
||||||
klog.Errorf("Datastore: %s is not compatible with Policy: %s", datastore.Name(), vmdisk.volumeOptions.StoragePolicyName)
|
|
||||||
return "", fmt.Errorf("user specified datastore is not compatible with the storagePolicy: %q. Failed with faults: %+q", vmdisk.volumeOptions.StoragePolicyName, faultMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storageProfileSpec := &types.VirtualMachineDefinedProfileSpec{}
|
|
||||||
// Is PBM storage policy ID is present, set the storage spec profile ID,
|
|
||||||
// else, set raw the VSAN policy string.
|
|
||||||
if vmdisk.volumeOptions.StoragePolicyID != "" {
|
|
||||||
storageProfileSpec.ProfileId = vmdisk.volumeOptions.StoragePolicyID
|
|
||||||
} else if vmdisk.volumeOptions.VSANStorageProfileData != "" {
|
|
||||||
// Check Datastore type - VSANStorageProfileData is only applicable to vSAN Datastore
|
|
||||||
dsType, err := datastore.GetType(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if dsType != vclib.VSANDatastoreType {
|
|
||||||
klog.Errorf("The specified datastore: %q is not a VSAN datastore", datastore.Name())
|
|
||||||
return "", fmt.Errorf("the specified datastore: %q is not a VSAN datastore."+
|
|
||||||
" the policy parameters will work only with VSAN Datastore."+
|
|
||||||
" so, please specify a valid VSAN datastore in Storage class definition", datastore.Name())
|
|
||||||
}
|
|
||||||
storageProfileSpec.ProfileId = ""
|
|
||||||
storageProfileSpec.ProfileData = &types.VirtualMachineProfileRawData{
|
|
||||||
ExtensionKey: "com.vmware.vim.sps",
|
|
||||||
ObjectData: vmdisk.volumeOptions.VSANStorageProfileData,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
klog.Errorf("Both volumeOptions.StoragePolicyID and volumeOptions.VSANStorageProfileData are not set. One of them should be set")
|
|
||||||
return "", fmt.Errorf("both volumeOptions.StoragePolicyID and volumeOptions.VSANStorageProfileData are not set. One of them should be set")
|
|
||||||
}
|
|
||||||
var dummyVM *vclib.VirtualMachine
|
|
||||||
// Check if VM already exist in the folder.
|
|
||||||
// If VM is already present, use it, else create a new dummy VM.
|
|
||||||
fnvHash := fnv.New32a()
|
|
||||||
fnvHash.Write([]byte(vmdisk.volumeOptions.Name))
|
|
||||||
dummyVMFullName := vclib.DummyVMPrefixName + "-" + fmt.Sprint(fnvHash.Sum32())
|
|
||||||
dummyVM, err = datastore.Datacenter.GetVMByPath(ctx, vmdisk.vmOptions.VMFolder.InventoryPath+"/"+dummyVMFullName)
|
|
||||||
if err != nil {
|
|
||||||
// Create a dummy VM
|
|
||||||
klog.V(1).Infof("Creating Dummy VM: %q", dummyVMFullName)
|
|
||||||
dummyVM, err = vmdisk.createDummyVM(ctx, datastore.Datacenter, dummyVMFullName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("failed to create Dummy VM. err: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconfigure the VM to attach the disk with the VSAN policy configured
|
|
||||||
virtualMachineConfigSpec := types.VirtualMachineConfigSpec{}
|
|
||||||
disk, _, err := dummyVM.CreateDiskSpec(ctx, vmdisk.diskPath, datastore, vmdisk.volumeOptions)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("failed to create Disk Spec. err: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
deviceConfigSpec := &types.VirtualDeviceConfigSpec{
|
|
||||||
Device: disk,
|
|
||||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
|
||||||
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceConfigSpec.Profile = append(deviceConfigSpec.Profile, storageProfileSpec)
|
|
||||||
virtualMachineConfigSpec.DeviceChange = append(virtualMachineConfigSpec.DeviceChange, deviceConfigSpec)
|
|
||||||
fileAlreadyExist := false
|
|
||||||
task, err := dummyVM.Reconfigure(ctx, virtualMachineConfigSpec)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to reconfig. err: %v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = task.Wait(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fileAlreadyExist = isAlreadyExists(vmdisk.diskPath, err)
|
|
||||||
if fileAlreadyExist {
|
|
||||||
//Skip error and continue to detach the disk as the disk was already created on the datastore.
|
|
||||||
klog.V(vclib.LogLevel).Infof("File: %v already exists", vmdisk.diskPath)
|
|
||||||
} else {
|
|
||||||
klog.Errorf("Failed to attach the disk to VM: %q with err: %+v", dummyVMFullName, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Detach the disk from the dummy VM.
|
|
||||||
err = dummyVM.DetachDisk(ctx, vmdisk.diskPath)
|
|
||||||
if err != nil {
|
|
||||||
if vclib.DiskNotFoundErrMsg == err.Error() && fileAlreadyExist {
|
|
||||||
// Skip error if disk was already detached from the dummy VM but still present on the datastore.
|
|
||||||
klog.V(vclib.LogLevel).Infof("File: %v is already detached", vmdisk.diskPath)
|
|
||||||
} else {
|
|
||||||
klog.Errorf("Failed to detach the disk: %q from VM: %q with err: %+v", vmdisk.diskPath, dummyVMFullName, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Delete the dummy VM
|
|
||||||
err = dummyVM.DeleteVM(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to destroy the vm: %q with err: %+v", dummyVMFullName, err)
|
|
||||||
}
|
|
||||||
return vmdisk.diskPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vmdisk vmDiskManager) Delete(ctx context.Context, datacenter *vclib.Datacenter) error {
|
|
||||||
return fmt.Errorf("vmDiskManager.Delete is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDummyVM create a Dummy VM at specified location with given name.
|
|
||||||
func (vmdisk vmDiskManager) createDummyVM(ctx context.Context, datacenter *vclib.Datacenter, vmName string) (*vclib.VirtualMachine, error) {
|
|
||||||
// Create a virtual machine config spec with 1 SCSI adapter.
|
|
||||||
virtualMachineConfigSpec := types.VirtualMachineConfigSpec{
|
|
||||||
Name: vmName,
|
|
||||||
Files: &types.VirtualMachineFileInfo{
|
|
||||||
VmPathName: "[" + vmdisk.volumeOptions.Datastore + "]",
|
|
||||||
},
|
|
||||||
NumCPUs: 1,
|
|
||||||
MemoryMB: 4,
|
|
||||||
DeviceChange: []types.BaseVirtualDeviceConfigSpec{
|
|
||||||
&types.VirtualDeviceConfigSpec{
|
|
||||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
|
||||||
Device: &types.ParaVirtualSCSIController{
|
|
||||||
VirtualSCSIController: types.VirtualSCSIController{
|
|
||||||
SharedBus: types.VirtualSCSISharingNoSharing,
|
|
||||||
VirtualController: types.VirtualController{
|
|
||||||
BusNumber: 0,
|
|
||||||
VirtualDevice: types.VirtualDevice{
|
|
||||||
Key: 1000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
task, err := vmdisk.vmOptions.VMFolder.CreateVM(ctx, virtualMachineConfigSpec, vmdisk.vmOptions.VMResourcePool, nil)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create VM. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dummyVMTaskInfo, err := task.WaitForResult(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while waiting for create VM task result. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vmRef := dummyVMTaskInfo.Result.(object.Reference)
|
|
||||||
dummyVM := object.NewVirtualMachine(datacenter.Client(), vmRef.Reference())
|
|
||||||
return &vclib.VirtualMachine{VirtualMachine: dummyVM, Datacenter: datacenter}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanUpDummyVMs deletes stale dummyVM's
|
|
||||||
func CleanUpDummyVMs(ctx context.Context, folder *vclib.Folder) error {
|
|
||||||
vmList, err := folder.GetVirtualMachines(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Failed to get virtual machines in the kubernetes cluster: %s, err: %+v", folder.InventoryPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vmList == nil || len(vmList) == 0 {
|
|
||||||
klog.Errorf("No virtual machines found in the kubernetes cluster: %s", folder.InventoryPath)
|
|
||||||
return fmt.Errorf("no virtual machines found in the kubernetes cluster: %s", folder.InventoryPath)
|
|
||||||
}
|
|
||||||
var dummyVMList []*vclib.VirtualMachine
|
|
||||||
// Loop through VM's in the Kubernetes cluster to find dummy VM's
|
|
||||||
for _, vm := range vmList {
|
|
||||||
vmName, err := vm.ObjectName(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Unable to get name from VM with err: %+v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(vmName, vclib.DummyVMPrefixName) {
|
|
||||||
vmObj := vclib.VirtualMachine{VirtualMachine: object.NewVirtualMachine(folder.Client(), vm.Reference())}
|
|
||||||
dummyVMList = append(dummyVMList, &vmObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, vm := range dummyVMList {
|
|
||||||
err = vm.DeleteVM(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Unable to delete dummy VM with err: %+v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAlreadyExists(path string, err error) bool {
|
|
||||||
errorMessage := fmt.Sprintf("Cannot complete the operation because the file or folder %s already exists", path)
|
|
||||||
if errorMessage == err.Error() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Folder extends the govmomi Folder object
|
|
||||||
type Folder struct {
|
|
||||||
*object.Folder
|
|
||||||
Datacenter *Datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVirtualMachines returns list of VirtualMachine inside a folder.
|
|
||||||
func (folder *Folder) GetVirtualMachines(ctx context.Context) ([]*VirtualMachine, error) {
|
|
||||||
vmFolders, err := folder.Children(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get children from Folder: %s. err: %+v", folder.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var vmObjList []*VirtualMachine
|
|
||||||
for _, vmFolder := range vmFolders {
|
|
||||||
if vmFolder.Reference().Type == VirtualMachineType {
|
|
||||||
vmObj := VirtualMachine{object.NewVirtualMachine(folder.Client(), vmFolder.Reference()), folder.Datacenter}
|
|
||||||
vmObjList = append(vmObjList, &vmObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vmObjList, nil
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/simulator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFolder(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
model := simulator.VPX()
|
|
||||||
// Child folder "F0" will be created under the root folder and datacenter folders,
|
|
||||||
// and all resources are created within the "F0" child folders.
|
|
||||||
model.Folder = 1
|
|
||||||
|
|
||||||
defer model.Remove()
|
|
||||||
err := model.Create()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := model.Service.NewServer()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
c, err := govmomi.NewClient(ctx, s.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vc := &VSphereConnection{Client: c.Client}
|
|
||||||
|
|
||||||
dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const folderName = "F0"
|
|
||||||
vmFolder := path.Join("/", folderName, dc.Name(), "vm")
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
folderPath string
|
|
||||||
expect int
|
|
||||||
}{
|
|
||||||
{vmFolder, 0},
|
|
||||||
{path.Join(vmFolder, folderName), (model.Host + model.Cluster) * model.Machine},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
folder, cerr := dc.GetFolderByPath(ctx, test.folderPath)
|
|
||||||
if cerr != nil {
|
|
||||||
t.Fatal(cerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
vms, cerr := folder.GetVirtualMachines(ctx)
|
|
||||||
if cerr != nil {
|
|
||||||
t.Fatalf("%d: %s", i, cerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vms) != test.expect {
|
|
||||||
t.Errorf("%d: expected %d VMs, got: %d", i, test.expect, len(vms))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/pbm"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
pbmtypes "github.com/vmware/govmomi/pbm/types"
|
|
||||||
"github.com/vmware/govmomi/vim25"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PbmClient is extending govmomi pbm, and provides functions to get compatible list of datastore for given policy
|
|
||||||
type PbmClient struct {
|
|
||||||
*pbm.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPbmClient returns a new PBM Client object
|
|
||||||
func NewPbmClient(ctx context.Context, client *vim25.Client) (*PbmClient, error) {
|
|
||||||
pbmClient, err := pbm.NewClient(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create new Pbm Client. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PbmClient{pbmClient}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDatastoreCompatible check if the datastores is compatible for given storage policy id
|
|
||||||
// if datastore is not compatible with policy, fault message with the Datastore Name is returned
|
|
||||||
func (pbmClient *PbmClient) IsDatastoreCompatible(ctx context.Context, storagePolicyID string, datastore *Datastore) (bool, string, error) {
|
|
||||||
faultMessage := ""
|
|
||||||
placementHub := pbmtypes.PbmPlacementHub{
|
|
||||||
HubType: datastore.Reference().Type,
|
|
||||||
HubId: datastore.Reference().Value,
|
|
||||||
}
|
|
||||||
hubs := []pbmtypes.PbmPlacementHub{placementHub}
|
|
||||||
req := []pbmtypes.BasePbmPlacementRequirement{
|
|
||||||
&pbmtypes.PbmPlacementCapabilityProfileRequirement{
|
|
||||||
ProfileId: pbmtypes.PbmProfileId{
|
|
||||||
UniqueId: storagePolicyID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
compatibilityResult, err := pbmClient.CheckRequirements(ctx, hubs, nil, req)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred for CheckRequirements call. err %+v", err)
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
if compatibilityResult != nil && len(compatibilityResult) > 0 {
|
|
||||||
compatibleHubs := compatibilityResult.CompatibleDatastores()
|
|
||||||
if compatibleHubs != nil && len(compatibleHubs) > 0 {
|
|
||||||
return true, "", nil
|
|
||||||
}
|
|
||||||
dsName, err := datastore.ObjectName(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get datastore ObjectName")
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
if compatibilityResult[0].Error[0].LocalizedMessage == "" {
|
|
||||||
faultMessage = "Datastore: " + dsName + " is not compatible with the storage policy."
|
|
||||||
} else {
|
|
||||||
faultMessage = "Datastore: " + dsName + " is not compatible with the storage policy. LocalizedMessage: " + compatibilityResult[0].Error[0].LocalizedMessage + "\n"
|
|
||||||
}
|
|
||||||
return false, faultMessage, nil
|
|
||||||
}
|
|
||||||
return false, "", fmt.Errorf("compatibilityResult is nil or empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCompatibleDatastores filters and returns compatible list of datastores for given storage policy id
|
|
||||||
// For Non Compatible Datastores, fault message with the Datastore Name is also returned
|
|
||||||
func (pbmClient *PbmClient) GetCompatibleDatastores(ctx context.Context, storagePolicyID string, datastores []*DatastoreInfo) ([]*DatastoreInfo, string, error) {
|
|
||||||
var (
|
|
||||||
dsMorNameMap = getDsMorNameMap(ctx, datastores)
|
|
||||||
localizedMessagesForNotCompatibleDatastores = ""
|
|
||||||
)
|
|
||||||
compatibilityResult, err := pbmClient.GetPlacementCompatibilityResult(ctx, storagePolicyID, datastores)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while retrieving placement compatibility result for datastores: %+v with storagePolicyID: %s. err: %+v", datastores, storagePolicyID, err)
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
compatibleHubs := compatibilityResult.CompatibleDatastores()
|
|
||||||
var compatibleDatastoreList []*DatastoreInfo
|
|
||||||
for _, hub := range compatibleHubs {
|
|
||||||
compatibleDatastoreList = append(compatibleDatastoreList, getDatastoreFromPlacementHub(datastores, hub))
|
|
||||||
}
|
|
||||||
for _, res := range compatibilityResult {
|
|
||||||
for _, err := range res.Error {
|
|
||||||
dsName := dsMorNameMap[res.Hub.HubId]
|
|
||||||
localizedMessage := ""
|
|
||||||
if err.LocalizedMessage != "" {
|
|
||||||
localizedMessage = "Datastore: " + dsName + " not compatible with the storage policy. LocalizedMessage: " + err.LocalizedMessage + "\n"
|
|
||||||
} else {
|
|
||||||
localizedMessage = "Datastore: " + dsName + " not compatible with the storage policy. \n"
|
|
||||||
}
|
|
||||||
localizedMessagesForNotCompatibleDatastores += localizedMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return an error if there are no compatible datastores.
|
|
||||||
if len(compatibleHubs) < 1 {
|
|
||||||
klog.Errorf("No compatible datastores found that satisfy the storage policy requirements: %s", storagePolicyID)
|
|
||||||
return nil, localizedMessagesForNotCompatibleDatastores, fmt.Errorf("No compatible datastores found that satisfy the storage policy requirements")
|
|
||||||
}
|
|
||||||
return compatibleDatastoreList, localizedMessagesForNotCompatibleDatastores, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPlacementCompatibilityResult gets placement compatibility result based on storage policy requirements.
|
|
||||||
func (pbmClient *PbmClient) GetPlacementCompatibilityResult(ctx context.Context, storagePolicyID string, datastore []*DatastoreInfo) (pbm.PlacementCompatibilityResult, error) {
|
|
||||||
var hubs []pbmtypes.PbmPlacementHub
|
|
||||||
for _, ds := range datastore {
|
|
||||||
hubs = append(hubs, pbmtypes.PbmPlacementHub{
|
|
||||||
HubType: ds.Reference().Type,
|
|
||||||
HubId: ds.Reference().Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
req := []pbmtypes.BasePbmPlacementRequirement{
|
|
||||||
&pbmtypes.PbmPlacementCapabilityProfileRequirement{
|
|
||||||
ProfileId: pbmtypes.PbmProfileId{
|
|
||||||
UniqueId: storagePolicyID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res, err := pbmClient.CheckRequirements(ctx, hubs, nil, req)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred for CheckRequirements call. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDataStoreForPlacementHub returns matching datastore associated with given pbmPlacementHub
|
|
||||||
func getDatastoreFromPlacementHub(datastore []*DatastoreInfo, pbmPlacementHub pbmtypes.PbmPlacementHub) *DatastoreInfo {
|
|
||||||
for _, ds := range datastore {
|
|
||||||
if ds.Reference().Type == pbmPlacementHub.HubType && ds.Reference().Value == pbmPlacementHub.HubId {
|
|
||||||
return ds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDsMorNameMap returns map of ds Mor and Datastore Object Name
|
|
||||||
func getDsMorNameMap(ctx context.Context, datastores []*DatastoreInfo) map[string]string {
|
|
||||||
dsMorNameMap := make(map[string]string)
|
|
||||||
for _, ds := range datastores {
|
|
||||||
dsObjectName, err := ds.ObjectName(ctx)
|
|
||||||
if err == nil {
|
|
||||||
dsMorNameMap[ds.Reference().Value] = dsObjectName
|
|
||||||
} else {
|
|
||||||
klog.Errorf("Error occurred while getting datastore object name. err: %+v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dsMorNameMap
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Keys in this directory are generated for testing purposes only.
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJJwIBAAKCAgEA4CKLwCPwMUIVaGhvZxLmXEzDflILVaGCZRRBbfYucfysylT/
|
|
||||||
JKPMlKs3ORNVW1cdiW1z/ZUlAlN+eqq40WSVQJqLUeXltsfZwemdFmf3SAWIu9v9
|
|
||||||
wI5mhLQJMh2XPKNILCBhrET/ANLVPbObJUFvGavpR9XVXTXsLUvuCR+oSpDvQYyn
|
|
||||||
WKJ5dAwqKaFx3GCEFAm0dNnSzliQrzKFOE0DUMxFQH5Lt2EYLHrya+K4ZtYbX5nK
|
|
||||||
X++T9R5pZs0npqmTQS/rIffv2hT89tKdqPz/MCt5xwmjsAO2uri5O+WaLUIkf8Bd
|
|
||||||
fmVAusE/5v2p3x3MH0rUXaNPg7FqLj1cnbcwHqqt3PmVl9VZINkPbnHHiua21GNq
|
|
||||||
DAZ/G/vP8/hlXwIeE8d6YPsSPm4NEH0Ku+yk0TEL6QkGFMYYpyCw1BNYGXd+zvf1
|
|
||||||
xjZtGrcViHhesxuv71nGdJbNSi7zwkYXydSKCNnjJ+Oqyip5uUC+DmydqcJTQLcZ
|
|
||||||
W5ObNfeB8PLz6UuVidMffh8evE13L60cS5wZyZWherMqB+I/uREt05gikCtlJVOo
|
|
||||||
shuLS0QjbK/INYCSFBJjt0xrwTbw13SQsEhktQYdqTHaDBWi6uh+pcY9msF1jZJ+
|
|
||||||
GAEPYcLzK3o2/kE6g09TZ3QDeP9bEDTllL+mIs4JGiWGNC/eGjGfyyAnfmECAwEA
|
|
||||||
AQKCAf88aRNBtm4G2MjsWzmrjmyIdCg84+AqNF3w4ITCHphmILRx1HbwaTW63GsF
|
|
||||||
9zAKbnCHmfipYImZFugAKAOobHPN9dmXOV+w5CzNFyo/38XGo7c26xR50efP3Lad
|
|
||||||
y1v3/Ap32kJ5LB+PGURgXQh0Ai7vvGYj9n6LoP0HOG/wBZhWgLn78O0p9qDFpoG2
|
|
||||||
tsz5mQoAXJ1G4W7wLu7QSc2eXyOFo4kG2QOPaZwaYQj2CyWokgzOt6TUNr6qUogW
|
|
||||||
LTWCtjH6X/AAN9Nt9Do6TIoyAf7F/PHVs8NqrZWSvjcu7bOgfzNXO4H3j1LjAzM2
|
|
||||||
Dyi5+k4KISEcG+hSln8H94H/AGD3Yea44sDnIZoOtKTB+O7V+jyU7qwtX9QaEu04
|
|
||||||
CslnZOun0/PR/C9mI7QaGu1YJcxdIw9Nlj07+iAzI4ZjuO+qHeUM7SNvH/MVbglA
|
|
||||||
2ZDkp7J3VlJgFObvHdINZMWNO1FIg/pc7TcXNsUkNAwnCwLh6//5/cZF+BtGlc4A
|
|
||||||
SGkhYKX3dRp8qLjNKxux3VHROoDByJDEUcnn0fEAm9aMbV+PofpghJtQqnKbsMn8
|
|
||||||
iF29v+9+JBIHFxAwhCIv9atF82VHt/sGPcsRqohttRWJDaUMBM3N8rvciiigcYzh
|
|
||||||
c/o4kH0YNoFSs4+evhYQDxk8yIGsgyuGfnW5QaLUIPa2AxblAoIBAQDyfoJr3UFq
|
|
||||||
LfkTkYHjAo4eua1vzlM3/8aFFnuQhMeoFvw4aA26x1DsUUozIRXTWWbnFH6GY8T3
|
|
||||||
B46jgWcO4UaMqbxQxTpHSDQFSVn/budugxGU70WQ9LcjSobk9uCXgk2MmRn9tA/6
|
|
||||||
+ergswSEuPxyNzgDF60BTrS5V2Akh6eF/sYZWnMKazZ3kdw1V5Y/IxfNH1yo6GRz
|
|
||||||
PTPVyyX6kU3+DNQSplgcsKYFhyoT2HPIRaxR1fTIw9E5w1rQWanYz/A0I3SDECsc
|
|
||||||
qJDy1rzC+0Tye2XLcWzHu5l1ng8GPLQJfjEtMTKXMIHjpLFC1P4hXNrlxTOnALSS
|
|
||||||
95bwzvDqfxULAoIBAQDsnkUVOvoXrE9xRI2EyWi1K08i5YSwy3rtV+uJP2Zyy4uB
|
|
||||||
r3TfzxFnYdXWykzHJGqHd6N5M6vCmbcLMS0G9z5QpDhrIF5vk26P9isvZ3k7rkWG
|
|
||||||
jgwif3kBcPQXlCDgwwnVmGsBf/A+2z3HOfNPK3Iy3VamFvYD52wgL8+N0puZ42aU
|
|
||||||
aH759JjLGcaVZWzWNdIcpS1OsBucGXCj3IeHmLjhJFbVebIHJ4rCs7gj51H8R8uk
|
|
||||||
fksxsgfPdRRpYq7NkDOzVDPb/KtTf5C4ZDogRaxj765DMnn6LhBFQVuDWEDJgjlF
|
|
||||||
Aolt8ynskf3xd19nlX99QAzXnql6LLClwps6G8XDAoIBADzhslDufevwmuZk091w
|
|
||||||
2MmyCG9Xt+EJYIgtetxv2cjD7JMk3L2WKSULy7tGhTpI6eL+bD3FcsAqr48xf/Rm
|
|
||||||
btYGD3ef7N/Uqurg3a2Z5JUEZzejUy3vosNDhNabfQvM9TdlgPcHbDOw511+1JWV
|
|
||||||
9Bug7XkpSpBXeFxIKaVCQbcMniPjZ5qoDEa84jKqSNiVMPaY9ySZJA8iwI7esCxW
|
|
||||||
quQryFreVKTvXN9qbhAJehhAFeF9/DUjpLYB7Bz/RftfSYltlWUKfCh30dyGOWIi
|
|
||||||
v865WHdZhNwop4C2LEN+nhz8B9C212LKFPJYeQC0hRFPRM4HUs6NCMkVTFotOqNF
|
|
||||||
QL0CggEAGXBysPOkS8NEz0K1jF8zGLdNTM0sVO2ri7T2J81fMFxd5VV91Uon7tt/
|
|
||||||
6BXb51Us9t+P/cnmX4ezPErPMn6GfpkJT8stHAXXzzaCMhiH2jjEVNEU0Oivk84X
|
|
||||||
ECnm1wNhHUvDxWeB5uAfZjn+xLZBEuLlG/o//O92modJY1APVp4yOyZ48FqxyrQ8
|
|
||||||
u3cqGmWy701674jTjxbVG2jsUVHEHsCPbWgmEcrYilJUK9gE4oC9jjPd1bv0RwOp
|
|
||||||
bCMl9Afa5x7YbIBf0xxV7N0puqqC/EOakrLslk85hJigRCDK5l9P1PGO4PlRupN/
|
|
||||||
n+Rbp4FVMZwfRVdTlUUUwN2JXtf5jQKCAQEAqSMv1mkLS3qnmW1E/qAYrEmMlHZo
|
|
||||||
253wuwsO0XS7xCxcEumIvjYCvhnHPYIO2rqsscmk42gYe/OUfteMb71BJ+HnlyOo
|
|
||||||
9oDbZg8W2DSUzTUy0yT/JMcNTwVCPeVj+bZ/LzDP5jKmZ7vXZkLGQCgU6ENVmsCg
|
|
||||||
b8nKz0xc7o8jERaSGY+h3LthXF0wAZJ3NdbnJjFbL8hYpwTrD6xd/yg3M5grrCLe
|
|
||||||
iBKfdpCIN6VrqI9VymoPZryb1OVEiClt0LHWTIXQPcH2J/CrMeWoGhRBW3yTAECf
|
|
||||||
HPhYMZddW2y6uOFjRcUCu2HG35ogEYlDd0kjH1HhPC2xXcFQBmOyPpEeDQ==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIE/jCCAuYCCQDRJ2qPhdmG0DANBgkqhkiG9w0BAQsFADBAMQswCQYDVQQGEwJV
|
|
||||||
UzELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkFjbWUsIEluYy4xDzANBgNVBAMMBnNv
|
|
||||||
bWVDQTAgFw0xODA2MDgxMzM5MjFaGA8yMjE4MDQyMTEzMzkyMVowQDELMAkGA1UE
|
|
||||||
BhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQKDApBY21lLCBJbmMuMQ8wDQYDVQQD
|
|
||||||
DAZzb21lQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDgIovAI/Ax
|
|
||||||
QhVoaG9nEuZcTMN+UgtVoYJlFEFt9i5x/KzKVP8ko8yUqzc5E1VbVx2JbXP9lSUC
|
|
||||||
U356qrjRZJVAmotR5eW2x9nB6Z0WZ/dIBYi72/3AjmaEtAkyHZc8o0gsIGGsRP8A
|
|
||||||
0tU9s5slQW8Zq+lH1dVdNewtS+4JH6hKkO9BjKdYonl0DCopoXHcYIQUCbR02dLO
|
|
||||||
WJCvMoU4TQNQzEVAfku3YRgsevJr4rhm1htfmcpf75P1HmlmzSemqZNBL+sh9+/a
|
|
||||||
FPz20p2o/P8wK3nHCaOwA7a6uLk75ZotQiR/wF1+ZUC6wT/m/anfHcwfStRdo0+D
|
|
||||||
sWouPVydtzAeqq3c+ZWX1Vkg2Q9ucceK5rbUY2oMBn8b+8/z+GVfAh4Tx3pg+xI+
|
|
||||||
bg0QfQq77KTRMQvpCQYUxhinILDUE1gZd37O9/XGNm0atxWIeF6zG6/vWcZ0ls1K
|
|
||||||
LvPCRhfJ1IoI2eMn46rKKnm5QL4ObJ2pwlNAtxlbk5s194Hw8vPpS5WJ0x9+Hx68
|
|
||||||
TXcvrRxLnBnJlaF6syoH4j+5ES3TmCKQK2UlU6iyG4tLRCNsr8g1gJIUEmO3TGvB
|
|
||||||
NvDXdJCwSGS1Bh2pMdoMFaLq6H6lxj2awXWNkn4YAQ9hwvMrejb+QTqDT1NndAN4
|
|
||||||
/1sQNOWUv6YizgkaJYY0L94aMZ/LICd+YQIDAQABMA0GCSqGSIb3DQEBCwUAA4IC
|
|
||||||
AQBYBRH/q3gB4gEiOAUl9HbnoUb7MznZ0uQTH7fUYqr66ceZkg9w1McbwiAeZAaY
|
|
||||||
qQWwr3u4A8/Bg8csE2yQTsXeA33FP3Q6obyuYn4q7e++4+9SLkbSSQfbB67pGUK5
|
|
||||||
/pal6ULrLGzs69fbL1tOaA/VKQJndg3N9cftyiIUWTzHDop8SLmIobWVRtPQHf00
|
|
||||||
oKq8loakyluQdxQxnGdl7vMXwSpSpIH84TOdy2JN90MzVLgOz55sb/wRYfhClNFD
|
|
||||||
+1sb2V4nL2w1kXaO2UVPzk7qpG5FE54JPvvN67Ec4JjMSnGo8l3dJ9jGEmgBIML3
|
|
||||||
l1onrti2HStSs1vR4Ax0xok08okRlrGA4FqQiSx853T5uLa/JLmWfLKg9ixR4ZV+
|
|
||||||
dF+2ZrFwDLZUr4VeaDd2v2mQFBNLvdZrqp1OZ4B/1+H5S8ucb+oVhGqzDkEvRCc+
|
|
||||||
WYpNxx7kpwZPTLmMYTXXKdTWfpgz9GL0LSkY8d1rxLwHxtV8EzAkV+zIWix4h/IE
|
|
||||||
0FG4WvhrttMCu8ulZhGGoVqy7gdb4+ViWnUYNuCCjIcRJj7SeZaDawBASa/jZwik
|
|
||||||
Hxrwn0osGUqEUBmvjDdXJpTaKCr2GFOvhCM2pG6AXa14b5hS2DgbX+NZYcScYtVC
|
|
||||||
vn2HMDjnIEF4uOfDJU5eLok4jli5+VwzOQ7hOHs3DIm4+g==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Copyright 2018 The Kubernetes 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.
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
readonly VALID_DAYS='73000'
|
|
||||||
readonly RSA_KEY_SIZE='4096'
|
|
||||||
|
|
||||||
createKey() {
|
|
||||||
openssl genrsa \
|
|
||||||
-out "$1" \
|
|
||||||
"$RSA_KEY_SIZE"
|
|
||||||
}
|
|
||||||
|
|
||||||
createCaCert() {
|
|
||||||
openssl req \
|
|
||||||
-x509 \
|
|
||||||
-subj "$( getSubj 'someCA' )" \
|
|
||||||
-new \
|
|
||||||
-nodes \
|
|
||||||
-key "$2" \
|
|
||||||
-sha256 \
|
|
||||||
-days "$VALID_DAYS" \
|
|
||||||
-out "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
createCSR() {
|
|
||||||
openssl req \
|
|
||||||
-new \
|
|
||||||
-sha256 \
|
|
||||||
-key "$2" \
|
|
||||||
-subj "$( getSubj 'localhost' )" \
|
|
||||||
-reqexts SAN \
|
|
||||||
-config <( getSANConfig ) \
|
|
||||||
-out "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
signCSR() {
|
|
||||||
openssl x509 \
|
|
||||||
-req \
|
|
||||||
-in "$2" \
|
|
||||||
-CA "$3" \
|
|
||||||
-CAkey "$4" \
|
|
||||||
-CAcreateserial \
|
|
||||||
-days "$VALID_DAYS" \
|
|
||||||
-sha256 \
|
|
||||||
-extfile <( getSAN ) \
|
|
||||||
-out "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
getSubj() {
|
|
||||||
local cn="${1:-someRandomCN}"
|
|
||||||
echo "/C=US/ST=CA/O=Acme, Inc./CN=${cn}"
|
|
||||||
}
|
|
||||||
|
|
||||||
getSAN() {
|
|
||||||
printf "subjectAltName=DNS:localhost,IP:127.0.0.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
getSANConfig() {
|
|
||||||
cat /etc/ssl/openssl.cnf
|
|
||||||
printf '\n[SAN]\n'
|
|
||||||
getSAN
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
local caCertPath="./ca.pem"
|
|
||||||
local caKeyPath="./ca.key"
|
|
||||||
local serverCsrPath="./server.csr"
|
|
||||||
local serverCertPath="./server.pem"
|
|
||||||
local serverKeyPath="./server.key"
|
|
||||||
|
|
||||||
createKey "$caKeyPath"
|
|
||||||
createCaCert "$caCertPath" "$caKeyPath"
|
|
||||||
createKey "$serverKeyPath"
|
|
||||||
createCSR "$serverCsrPath" "$serverKeyPath"
|
|
||||||
signCSR "$serverCertPath" "$serverCsrPath" "$caCertPath" "$caKeyPath"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
this is some invalid content
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE REQUEST-----
|
|
||||||
MIIEtTCCAp0CAQAwQzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQK
|
|
||||||
DApBY21lLCBJbmMuMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEB
|
|
||||||
AQUAA4ICDwAwggIKAoICAQCVkk5HMKNvMXVJoJcUfKK252UT6rdnlsaFLZOlcbp3
|
|
||||||
otqiq3A2jhQLeL5Ocyd22s/ak2RX9liK+ynV8fP3YWoUBP5elhwbykubiIvSTRS5
|
|
||||||
85Z0s9NfzscImMpnivt+bOy3KOoriy/0jfJ7WMqLRUTUEusXUpW8QT/U9cK6DrwQ
|
|
||||||
E/9oXTr669yvqjyFsxjOB0pLOFFib0LeQZxrA2h+oAP8qT/Of6kyTgGWjLhSC1cV
|
|
||||||
eCPZsSeZUT61FbIu/b5M42WYuddoFbf8y9m0oLeYizYob7poE25jw91bNa8y2nfS
|
|
||||||
v+JuCcfO4wq29cnldGFNpJPhBhc1sbBvVshXXKWdfzN1c8RCS5hNANy1phAJ7RFe
|
|
||||||
3Uj0WneBVBHHJMz7Qh61uxTST1W8HBDTuaBTxGKTcPFWd9u4lj/BEScRFOSC/qiO
|
|
||||||
1HCKzOsYhjnHfql5GzfQKpEy/e4m2oL8VTqcJBsfHCyxDIH+6Y3ovttymxAUPJ14
|
|
||||||
r3mG9FDLq1va/+8xzDswyjmRIVQeOgvllzgM5vCKqz6nsXtLRYgkwHMk5yOaAIzO
|
|
||||||
BnsmZztsyaubjcYvM5pUsiO49VWk6ntiAn+WpF/sreFlesx1peQKbTVovwvn137d
|
|
||||||
V92Oncce+ZikKHxtz4qOz+dH1Fz7Ykor8fXcsfdbkKvwWdz8U/pOBu+83CxBXTWA
|
|
||||||
bwIDAQABoC0wKwYJKoZIhvcNAQkOMR4wHDAaBgNVHREEEzARgglsb2NhbGhvc3SH
|
|
||||||
BH8AAAEwDQYJKoZIhvcNAQELBQADggIBADgJfI3xRKlOInZQjg+afz+L477IiFmP
|
|
||||||
Pf0qwO/EqBkCmbDbmvXpXi/y9Ffh6bMx2naN873nW3k1uVG2W0O4Bl7di9PkmRxY
|
|
||||||
ktcWY+CaxDT5+Y3LmrqICgrZmELTuV5G8xX2/7bpdEtY4sWpoOeOun+CeGTCeUGx
|
|
||||||
sGxOWrhydYwrkowupPthYreIIBBPHWl2gEw/m+Y7aJZGtKnDD9eCbF6RxmXRWHDu
|
|
||||||
0Ly+F3veXbht9LjKPFsgfsogo33Nl8+W1LCActKNY7NMDdGkc+RqaTyxhYEwomui
|
|
||||||
N1NDOW1qHqSyp2RC13cXokfLL58WGXS6PpNhSln9u4ZG9a+TY+vw1qC//1CyTicY
|
|
||||||
ylyEn2qfqTSG3W7T/u6ZTL0MpMjFv8VigpffJcFDjq6lVH8LyTniSXdCREy78jAo
|
|
||||||
8O/2tzJtWrar8bbeN7KCwVcJVaK15a1GWZmo5Ei33U/2Tm+UyRbWL8eISO2Hs3WM
|
|
||||||
90aFPaHfqKpiPsJrnnOm270lZclgqEtpsyuLsAClqxytCYPw4zTa6WOfDJtmVUrT
|
|
||||||
1fvMjqwzvs7jbNrgfkwSxXiABwTMQQWeAtuSO+zZH4Ms10qyANoh4FFi/oS3dRKQ
|
|
||||||
0kdu7AsJqnou9q9HWq1WCTqMcyNE0KPHuo4xhtOlWoGbsugTs7XBml30D7bKJVfG
|
|
||||||
PazsY1b0/cx7
|
|
||||||
-----END CERTIFICATE REQUEST-----
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIJKAIBAAKCAgEAlZJORzCjbzF1SaCXFHyitudlE+q3Z5bGhS2TpXG6d6Laoqtw
|
|
||||||
No4UC3i+TnMndtrP2pNkV/ZYivsp1fHz92FqFAT+XpYcG8pLm4iL0k0UufOWdLPT
|
|
||||||
X87HCJjKZ4r7fmzstyjqK4sv9I3ye1jKi0VE1BLrF1KVvEE/1PXCug68EBP/aF06
|
|
||||||
+uvcr6o8hbMYzgdKSzhRYm9C3kGcawNofqAD/Kk/zn+pMk4Bloy4UgtXFXgj2bEn
|
|
||||||
mVE+tRWyLv2+TONlmLnXaBW3/MvZtKC3mIs2KG+6aBNuY8PdWzWvMtp30r/ibgnH
|
|
||||||
zuMKtvXJ5XRhTaST4QYXNbGwb1bIV1ylnX8zdXPEQkuYTQDctaYQCe0RXt1I9Fp3
|
|
||||||
gVQRxyTM+0IetbsU0k9VvBwQ07mgU8Rik3DxVnfbuJY/wREnERTkgv6ojtRwiszr
|
|
||||||
GIY5x36peRs30CqRMv3uJtqC/FU6nCQbHxwssQyB/umN6L7bcpsQFDydeK95hvRQ
|
|
||||||
y6tb2v/vMcw7MMo5kSFUHjoL5Zc4DObwiqs+p7F7S0WIJMBzJOcjmgCMzgZ7Jmc7
|
|
||||||
bMmrm43GLzOaVLIjuPVVpOp7YgJ/lqRf7K3hZXrMdaXkCm01aL8L59d+3Vfdjp3H
|
|
||||||
HvmYpCh8bc+Kjs/nR9Rc+2JKK/H13LH3W5Cr8Fnc/FP6TgbvvNwsQV01gG8CAwEA
|
|
||||||
AQKCAgBLBQn8DPo8YDsqxcBhRy45vQ/mkHiTHX3O+JAwkD1tmiI9Ku3qfxKwukwB
|
|
||||||
fyKRK6jLQdg3gljgxJ80Ltol/xc8mVCYUoQgsDOB/FfdEEpQBkw1lqhzSnxr5G7I
|
|
||||||
xl3kCHAmYgAp/PL9n2C620sj1YdzM1X06bgupy+D+gxEU/WhvtYBG5nklv6moSUg
|
|
||||||
DjdnxyJNXh7710Bbx97Tke8Ma+f0B1P4l/FeSN/lCgm9JPD11L9uhbuN28EvBIXN
|
|
||||||
qfmUCQ5BLx1KmHIi+n/kaCQN/+0XFQsS/oQEyA2znNaWFBu7egDxHji4nQoXwGoW
|
|
||||||
i2vujJibafmkNc5/2bA8mTx8JXvCLhU2L9j2ZumpKOda0g+pfMauesL+9rvZdqwW
|
|
||||||
gjdjndOHZlg3qm40hGCDBVmmV3mdnvXrk1BbuB4Y0N7qGo3PyYtJHGwJILaNQVGR
|
|
||||||
Sj75uTatxJwFXsqSaJaErV3Q90IiyXX4AOFGnWHOs29GEwtnDbCvT/rzqutTYSXD
|
|
||||||
Yv0XFDznzJelhZTH7FbaW3FW3YGEG1ER/0MtKpsAH4i7H9q3KKK8yrzUsgUkGwXt
|
|
||||||
xtoLckh91xilPIGbzARdELTEdHrjlFL+qaz3PIqEQScWz3WBu2JcIzGbp6PQfMZ+
|
|
||||||
FZXarEb/ADZuX0+WoKFYR5jzwMoQfF/fxe2Ib/37ETNw4BgfSQKCAQEAxOw64XgO
|
|
||||||
nUVJslzGK/H5fqTVpD1rfRmvVAiSDLAuWpClbpDZXqEPuoPPYsiccuUWu9VkJE1F
|
|
||||||
6MZEexGx1jFkN08QUHD1Bobzu6ThaBc2PrWHRjFGKM60d0AkhOiL4N04FGwVeCN6
|
|
||||||
xzIJFk1E4VOOo1+lzeAWRvi1lwuWTgQi+m25nwBJtmYdBLGeS+DXy80Fi6deECei
|
|
||||||
ipDzJ4rxJsZ61uqBeYC4CfuHW9m5rCzJWPMMMFrPdl3OxEyZzKng4Co5EYc5i/QH
|
|
||||||
piXD6IJayKcTPRK3tBJZp2YCIIdtQLcjAwmDEDowQtelHkbTihXMGRarf3VcOEoN
|
|
||||||
ozMRgcLEEynuKwKCAQEAwnF5ZkkJEL/1MCOZ6PZfSKl35ZMIz/4Umk8hOMAQGhCT
|
|
||||||
cnxlDUfGSBu4OihdBbIuBSBsYDjgcev8uyiIPDVy0FIkBKRGfgrNCLDh19aHljvE
|
|
||||||
bUc3akvbft0mro86AvSd/Rpc7sj841bru37RDUm6AJOtIvb6DWUpMOZgMm0WMmSI
|
|
||||||
kNs/UT+7rqg+AZPP8lumnJIFnRK38xOehQAaS1FHWGP//38py8yo8eXpMsoCWMch
|
|
||||||
c+kZD2jsAYV+SWjjkZjcrv/52+asd4AotRXIShV8E8xItQeq6vLHKOaIe0tC2Y44
|
|
||||||
ONAKiu4dgABt1voy8I5J63MwgeNmgAUS+KsgUclYzQKCAQEAlt/3bPAzIkQH5uQ1
|
|
||||||
4U2PvnxEQ4XbaQnYzyWR4K7LlQ/l8ASCxoHYLyr2JdVWKKFk/ZzNERMzUNk3dqNk
|
|
||||||
AZvuEII/GaKx2MJk04vMN5gxM3KZpinyeymEEynN0RbqtOpJITx+ZoGofB3V4IRr
|
|
||||||
FciTLJEH0+iwqMe9OXDjQ/rfYcfXw/7QezNZYFNF2RT3wWnfqdQduXrkig3sfotx
|
|
||||||
oCfJzgf2E0WPu/Y/CxyRqVzXF5N/7zxkX2gYF0YpQCmX5afz+X4FlTju81lT9DyL
|
|
||||||
mdiIYO6KWSkGD7+UOaAJEOA/rwAGrtQmTdAy7jONt+pjaYV4+DrO4UG7mSJzc1vq
|
|
||||||
JlSl6QKCAQARqwPv8mT7e6XI2QNMMs7XqGZ3mtOrKpguqVAIexM7exQazAjWmxX+
|
|
||||||
SV6FElPZh6Y82wRd/e0PDPVrADTY27ZyDXSuY0rwewTEbGYpGZo6YXXoxBbZ9sic
|
|
||||||
D3ZLWEJaMGYGsJWPMP4hni1PXSebwH5BPSn3Sl/QRcfnZJeLHXRt4cqy9uka9eKU
|
|
||||||
7T6tIAQ+LmvGQFJ4QlIqqTa3ORoqi9kiw/tn+OMQXKlhSZXWApsR/A4jHSQkzVDc
|
|
||||||
loeyHfDHsw8ia6oFfEFhnmiUg8UuTiN3HRHiOS8jqCnGoqP2KBGL+StMpkK++wH9
|
|
||||||
NozEgvmL+DHpTg8zTjlrGortw4btR5FlAoIBABVni+EsGA5K/PM1gIct2pDm+6Kq
|
|
||||||
UCYScTwIjftuwKLk/KqermG9QJLiJouKO3ZSz7iCelu87Dx1cKeXrc2LQ1pnQzCB
|
|
||||||
JnI6BCT+zRnQFXjLokJXD2hIS2hXhqV6/9FRXLKKMYePcDxWt/etLNGmpLnhDfb3
|
|
||||||
sMOH/9pnaGmtk36Ce03Hh7E1C6io/MKfTq+KKUV1UGwO1BdNQCiclkYzAUqn1O+Y
|
|
||||||
c8BaeGKc2c6as8DKrPTGGQGmzo/ZUxQVfVFl2g7+HXISWBBcui/G5gtnU1afZqbW
|
|
||||||
mTmDoqs4510vhlkhN9XZ0DyhewDIqNNGEY2vS1x2fJz1XC2Eve4KpSyUsiE=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFJjCCAw6gAwIBAgIJAOcEAbv8NslfMA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNV
|
|
||||||
BAYTAlVTMQswCQYDVQQIDAJDQTETMBEGA1UECgwKQWNtZSwgSW5jLjEPMA0GA1UE
|
|
||||||
AwwGc29tZUNBMCAXDTE4MDYwODEzMzkyNFoYDzIyMTgwNDIxMTMzOTI0WjBDMQsw
|
|
||||||
CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkFjbWUsIEluYy4xEjAQ
|
|
||||||
BgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
|
|
||||||
AJWSTkcwo28xdUmglxR8orbnZRPqt2eWxoUtk6Vxunei2qKrcDaOFAt4vk5zJ3ba
|
|
||||||
z9qTZFf2WIr7KdXx8/dhahQE/l6WHBvKS5uIi9JNFLnzlnSz01/OxwiYymeK+35s
|
|
||||||
7Lco6iuLL/SN8ntYyotFRNQS6xdSlbxBP9T1wroOvBAT/2hdOvrr3K+qPIWzGM4H
|
|
||||||
Sks4UWJvQt5BnGsDaH6gA/ypP85/qTJOAZaMuFILVxV4I9mxJ5lRPrUVsi79vkzj
|
|
||||||
ZZi512gVt/zL2bSgt5iLNihvumgTbmPD3Vs1rzLad9K/4m4Jx87jCrb1yeV0YU2k
|
|
||||||
k+EGFzWxsG9WyFdcpZ1/M3VzxEJLmE0A3LWmEAntEV7dSPRad4FUEcckzPtCHrW7
|
|
||||||
FNJPVbwcENO5oFPEYpNw8VZ327iWP8ERJxEU5IL+qI7UcIrM6xiGOcd+qXkbN9Aq
|
|
||||||
kTL97ibagvxVOpwkGx8cLLEMgf7pjei+23KbEBQ8nXiveYb0UMurW9r/7zHMOzDK
|
|
||||||
OZEhVB46C+WXOAzm8IqrPqexe0tFiCTAcyTnI5oAjM4GeyZnO2zJq5uNxi8zmlSy
|
|
||||||
I7j1VaTqe2ICf5akX+yt4WV6zHWl5AptNWi/C+fXft1X3Y6dxx75mKQofG3Pio7P
|
|
||||||
50fUXPtiSivx9dyx91uQq/BZ3PxT+k4G77zcLEFdNYBvAgMBAAGjHjAcMBoGA1Ud
|
|
||||||
EQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAgEABL8kffi7
|
|
||||||
48qSD+/l/UwCYdmqta1vAbOkvLnPtfXe1XlDpJipNuPxUBc8nNTemtrbg0erNJnC
|
|
||||||
jQHodqmdKBJJOdaEKTwAGp5pYvvjlU3WasmhfJy+QwOWgeqjJcTUo3+DEaHRls16
|
|
||||||
AZXlsp3hB6z0gzR/qzUuZwpMbL477JpuZtAcwLYeVvLG8bQRyWyEy8JgGDoYSn8s
|
|
||||||
Z16s+r6AX+cnL/2GHkZ+oc3iuXJbnac4xfWTKDiYnyzK6RWRnoyro7X0jiPz6XX3
|
|
||||||
wyoWzB1uMSCXscrW6ZcKyKqz75lySLuwGxOMhX4nGOoYHY0ZtrYn5WK2ZAJxsQnn
|
|
||||||
8QcjPB0nq37U7ifk1uebmuXe99iqyKnWaLvlcpe+HnO5pVxFkSQEf7Zh+hEnRDkN
|
|
||||||
IBzLFnqwDS1ug/oQ1aSvc8oBh2ylKDJuGtPNqGKibNJyb2diXO/aEUOKRUKPAxKa
|
|
||||||
dbKsc4Y1bhZNN3/MICMoyghwAOiuwUQMR5uhxTkQmZUwNrPFa+eW6GvyoYLFUsZs
|
|
||||||
hZfWLNGD5mLADElxs0HF7F9Zk6pSocTDXba4d4lfxsq88SyZZ7PbjJYFRfLQPzd1
|
|
||||||
CfvpRPqolEmZo1Y5Q644PELYiJRKpBxmX5GtC5j5eaUD9XdGKvXsGhb0m0gW75rq
|
|
||||||
iUnnLkZt2ya1cDJDiCnJjo7r5KxMo0XXFDc=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/find"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsNotFound return true if err is NotFoundError or DefaultNotFoundError
|
|
||||||
func IsNotFound(err error) bool {
|
|
||||||
_, ok := err.(*find.NotFoundError)
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok = err.(*find.DefaultNotFoundError)
|
|
||||||
if ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFinder(dc *Datacenter) *find.Finder {
|
|
||||||
finder := find.NewFinder(dc.Client(), false)
|
|
||||||
finder.SetDatacenter(dc.Datacenter)
|
|
||||||
return finder
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatVirtualDiskUUID removes any spaces and hyphens in UUID
|
|
||||||
// Example UUID input is 42375390-71f9-43a3-a770-56803bcd7baa and output after format is 4237539071f943a3a77056803bcd7baa
|
|
||||||
func formatVirtualDiskUUID(uuid string) string {
|
|
||||||
uuidwithNoSpace := strings.Replace(uuid, " ", "", -1)
|
|
||||||
uuidWithNoHypens := strings.Replace(uuidwithNoSpace, "-", "", -1)
|
|
||||||
return strings.ToLower(uuidWithNoHypens)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSCSIControllersOfType filters specific type of Controller device from given list of Virtual Machine Devices
|
|
||||||
func getSCSIControllersOfType(vmDevices object.VirtualDeviceList, scsiType string) []*types.VirtualController {
|
|
||||||
// get virtual scsi controllers of passed argument type
|
|
||||||
var scsiControllers []*types.VirtualController
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
devType := vmDevices.Type(device)
|
|
||||||
if devType == scsiType {
|
|
||||||
if c, ok := device.(types.BaseVirtualController); ok {
|
|
||||||
scsiControllers = append(scsiControllers, c.GetVirtualController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return scsiControllers
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAvailableSCSIController gets available SCSI Controller from list of given controllers, which has less than 15 disk devices.
|
|
||||||
func getAvailableSCSIController(scsiControllers []*types.VirtualController) *types.VirtualController {
|
|
||||||
// get SCSI controller which has space for adding more devices
|
|
||||||
for _, controller := range scsiControllers {
|
|
||||||
if len(controller.Device) < SCSIControllerDeviceLimit {
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNextUnitNumber gets the next available SCSI controller unit number from given list of Controller Device List
|
|
||||||
func getNextUnitNumber(devices object.VirtualDeviceList, c types.BaseVirtualController) (int32, error) {
|
|
||||||
var takenUnitNumbers [SCSIDeviceSlots]bool
|
|
||||||
takenUnitNumbers[SCSIReservedSlot] = true
|
|
||||||
key := c.GetVirtualController().Key
|
|
||||||
|
|
||||||
for _, device := range devices {
|
|
||||||
d := device.GetVirtualDevice()
|
|
||||||
if d.ControllerKey == key {
|
|
||||||
if d.UnitNumber != nil && *d.UnitNumber < SCSIDeviceSlots {
|
|
||||||
takenUnitNumbers[*d.UnitNumber] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for unitNumber, takenUnitNumber := range takenUnitNumbers {
|
|
||||||
if !takenUnitNumber {
|
|
||||||
return int32(unitNumber), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, fmt.Errorf("SCSI Controller with key=%d does not have any available slots", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSCSIControllers filters and return list of Controller Devices from given list of Virtual Machine Devices.
|
|
||||||
func getSCSIControllers(vmDevices object.VirtualDeviceList) []*types.VirtualController {
|
|
||||||
// get all virtual scsi controllers
|
|
||||||
var scsiControllers []*types.VirtualController
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
devType := vmDevices.Type(device)
|
|
||||||
switch devType {
|
|
||||||
case SCSIControllerType, strings.ToLower(LSILogicControllerType), strings.ToLower(BusLogicControllerType), PVSCSIControllerType, strings.ToLower(LSILogicSASControllerType):
|
|
||||||
if c, ok := device.(types.BaseVirtualController); ok {
|
|
||||||
scsiControllers = append(scsiControllers, c.GetVirtualController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return scsiControllers
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveStorageClusterORFolderNameFromVDiskPath removes the cluster or folder path from the vDiskPath
|
|
||||||
// for vDiskPath [DatastoreCluster/sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk, return value is [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk
|
|
||||||
// for vDiskPath [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk, return value remains same [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk
|
|
||||||
func RemoveStorageClusterORFolderNameFromVDiskPath(vDiskPath string) string {
|
|
||||||
datastore := regexp.MustCompile("\\[(.*?)\\]").FindStringSubmatch(vDiskPath)[1]
|
|
||||||
if filepath.Base(datastore) != datastore {
|
|
||||||
vDiskPath = strings.Replace(vDiskPath, datastore, filepath.Base(datastore), 1)
|
|
||||||
}
|
|
||||||
return vDiskPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPathFromVMDiskPath retrieves the path from VM Disk Path.
|
|
||||||
// Example: For vmDiskPath - [vsanDatastore] kubevols/volume.vmdk, the path is kubevols/volume.vmdk
|
|
||||||
func GetPathFromVMDiskPath(vmDiskPath string) string {
|
|
||||||
datastorePathObj := new(object.DatastorePath)
|
|
||||||
isSuccess := datastorePathObj.FromString(vmDiskPath)
|
|
||||||
if !isSuccess {
|
|
||||||
klog.Errorf("Failed to parse vmDiskPath: %s", vmDiskPath)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return datastorePathObj.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastorePathObjFromVMDiskPath gets the datastorePathObj from VM disk path.
|
|
||||||
func GetDatastorePathObjFromVMDiskPath(vmDiskPath string) (*object.DatastorePath, error) {
|
|
||||||
datastorePathObj := new(object.DatastorePath)
|
|
||||||
isSuccess := datastorePathObj.FromString(vmDiskPath)
|
|
||||||
if !isSuccess {
|
|
||||||
klog.Errorf("Failed to parse volPath: %s", vmDiskPath)
|
|
||||||
return nil, fmt.Errorf("failed to parse volPath: %s", vmDiskPath)
|
|
||||||
}
|
|
||||||
return datastorePathObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValidUUID checks if the string is a valid UUID.
|
|
||||||
func IsValidUUID(uuid string) bool {
|
|
||||||
r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$")
|
|
||||||
return r.MatchString(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsManagedObjectNotFoundError returns true if error is of type ManagedObjectNotFound
|
|
||||||
func IsManagedObjectNotFoundError(err error) bool {
|
|
||||||
isManagedObjectNotFoundError := false
|
|
||||||
if soap.IsSoapFault(err) {
|
|
||||||
_, isManagedObjectNotFoundError = soap.ToSoapFault(err).VimFault().(types.ManagedObjectNotFound)
|
|
||||||
}
|
|
||||||
return isManagedObjectNotFoundError
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInvalidCredentialsError returns true if error is of type InvalidLogin
|
|
||||||
func IsInvalidCredentialsError(err error) bool {
|
|
||||||
isInvalidCredentialsError := false
|
|
||||||
if soap.IsSoapFault(err) {
|
|
||||||
_, isInvalidCredentialsError = soap.ToSoapFault(err).VimFault().(types.InvalidLogin)
|
|
||||||
}
|
|
||||||
return isInvalidCredentialsError
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyVolumePathsForVMDevices verifies if the volume paths (volPaths) are attached to VM.
|
|
||||||
func VerifyVolumePathsForVMDevices(vmDevices object.VirtualDeviceList, volPaths []string, nodeName string, nodeVolumeMap map[string]map[string]bool) {
|
|
||||||
volPathsMap := make(map[string]bool)
|
|
||||||
for _, volPath := range volPaths {
|
|
||||||
volPathsMap[volPath] = true
|
|
||||||
}
|
|
||||||
// Verify if the volume paths are present on the VM backing virtual disk devices
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
if vmDevices.TypeName(device) == "VirtualDisk" {
|
|
||||||
virtualDevice := device.GetVirtualDevice()
|
|
||||||
if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
|
||||||
if volPathsMap[backing.FileName] {
|
|
||||||
setNodeVolumeMap(nodeVolumeMap, backing.FileName, nodeName, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// isvCenterNotSupported takes vCenter version and vCenter API version as input and return true if vCenter is no longer
|
|
||||||
// supported by VMware for in-tree vSphere volume plugin
|
|
||||||
func isvCenterNotSupported(vCenterVersion string, vCenterAPIVersion string) (bool, error) {
|
|
||||||
var vcversion, vcapiversion, minvcversion vcVersion
|
|
||||||
var err error
|
|
||||||
err = vcversion.parse(vCenterVersion)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to parse vCenter version: %s. err: %+v", vCenterVersion, err)
|
|
||||||
}
|
|
||||||
err = vcapiversion.parse(vCenterAPIVersion)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to parse vCenter API version: %s. err: %+v", vCenterAPIVersion, err)
|
|
||||||
}
|
|
||||||
err = minvcversion.parse(MinvCenterVersion)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to parse minimum vCenter version: %s. err: %+v", MinvCenterVersion, err)
|
|
||||||
}
|
|
||||||
if vcversion.isLessThan(minvcversion) && vcapiversion.isLessThan(minvcversion) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// vcVersion represents a VC version
|
|
||||||
type vcVersion struct {
|
|
||||||
Major int64
|
|
||||||
Minor int64
|
|
||||||
Revision int64
|
|
||||||
Build int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse helps parse version string to VCVersion
|
|
||||||
// returns error when parse fail
|
|
||||||
func (v *vcVersion) parse(version string) error {
|
|
||||||
for index, value := range strings.Split(version, ".") {
|
|
||||||
var err error
|
|
||||||
if index == 0 {
|
|
||||||
v.Major, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
} else if index == 1 {
|
|
||||||
v.Minor, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
} else if index == 2 {
|
|
||||||
v.Revision, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
} else if index == 3 {
|
|
||||||
v.Build, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse version: %q, err: %v", version, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isLessThan compares VCVersion v to o and returns
|
|
||||||
// true if v is less than o
|
|
||||||
func (v *vcVersion) isLessThan(o vcVersion) bool {
|
|
||||||
if v.Major != o.Major {
|
|
||||||
if v.Major > o.Major {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if v.Minor != o.Minor {
|
|
||||||
if v.Minor > o.Minor {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if v.Revision != o.Revision {
|
|
||||||
if v.Revision > o.Revision {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if v.Build != o.Build {
|
|
||||||
if v.Build > o.Build {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/simulator"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUtils(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
model := simulator.VPX()
|
|
||||||
// Child folder "F0" will be created under the root folder and datacenter folders,
|
|
||||||
// and all resources are created within the "F0" child folders.
|
|
||||||
model.Folder = 1
|
|
||||||
|
|
||||||
defer model.Remove()
|
|
||||||
err := model.Create()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := model.Service.NewServer()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
c, err := govmomi.NewClient(ctx, s.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vc := &VSphereConnection{Client: c.Client}
|
|
||||||
|
|
||||||
dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
finder := getFinder(dc)
|
|
||||||
datastores, err := finder.DatastoreList(ctx, "*")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
count := model.Count()
|
|
||||||
if count.Datastore != len(datastores) {
|
|
||||||
t.Errorf("got %d Datastores, expected: %d", len(datastores), count.Datastore)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = finder.Datastore(ctx, testNameNotFound)
|
|
||||||
if !IsNotFound(err) {
|
|
||||||
t.Errorf("unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsvCenterNotSupported(t *testing.T) {
|
|
||||||
type testsData struct {
|
|
||||||
vcVersion string
|
|
||||||
vcAPIVersion string
|
|
||||||
isNotSupported bool
|
|
||||||
}
|
|
||||||
testdataArray := []testsData{
|
|
||||||
{"8.0.0", "8.0.0.0", false},
|
|
||||||
{"7.0.3", "7.0.3.0", false},
|
|
||||||
{"7.0.2", "7.0.2.0", false},
|
|
||||||
{"7.0.1", "7.0.1.1", true},
|
|
||||||
{"7.0.0", "7.0.0.0", true},
|
|
||||||
{"6.7.0", "6.7.3", true},
|
|
||||||
{"6.7.0", "6.7", true},
|
|
||||||
{"6.7.0", "6.7.2", true},
|
|
||||||
{"6.7.0", "6.7.1", true},
|
|
||||||
{"6.5.0", "6.5", true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testdataArray {
|
|
||||||
notsupported, err := isvCenterNotSupported(test.vcVersion, test.vcAPIVersion)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if notsupported != test.isNotSupported {
|
|
||||||
t.Fatalf("test failed for vc version: %q and vc API version: %q",
|
|
||||||
test.vcVersion, test.vcAPIVersion)
|
|
||||||
} else {
|
|
||||||
t.Logf("test for vc version: %q and vc API version: %q passed. Is Not Supported : %v",
|
|
||||||
test.vcAPIVersion, test.vcAPIVersion, notsupported)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetNextUnitNumber(t *testing.T) {
|
|
||||||
type testData struct {
|
|
||||||
name string
|
|
||||||
deviceList object.VirtualDeviceList
|
|
||||||
expectValue int32
|
|
||||||
expectError bool
|
|
||||||
}
|
|
||||||
tests := []testData{
|
|
||||||
{
|
|
||||||
name: "should return 3 when devices 0-2 taken",
|
|
||||||
deviceList: generateVirtualDeviceList([]int32{0, 1, 2}),
|
|
||||||
expectValue: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "should return 0 when devices 1-3 taken",
|
|
||||||
deviceList: generateVirtualDeviceList([]int32{1, 2, 3}),
|
|
||||||
expectValue: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "should return error when no slots available",
|
|
||||||
deviceList: generateVirtualDeviceList([]int32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}),
|
|
||||||
expectValue: -1,
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "should ignore invalid UnitNumber in device list",
|
|
||||||
deviceList: generateVirtualDeviceList([]int32{0, 1, 16}),
|
|
||||||
expectValue: 2,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
controller := &types.VirtualController{}
|
|
||||||
for _, test := range tests {
|
|
||||||
val, err := getNextUnitNumber(test.deviceList, controller)
|
|
||||||
if err != nil && !test.expectError {
|
|
||||||
t.Fatalf("%s: unexpected error: %v", test.name, err)
|
|
||||||
}
|
|
||||||
if val != test.expectValue {
|
|
||||||
t.Fatalf("%s: expected value %v but got %v", test.name, test.expectValue, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateVirtualDeviceList(unitNumbers []int32) object.VirtualDeviceList {
|
|
||||||
deviceList := object.VirtualDeviceList{}
|
|
||||||
for _, val := range unitNumbers {
|
|
||||||
unitNum := val
|
|
||||||
dev := &types.VirtualDevice{
|
|
||||||
Key: unitNum,
|
|
||||||
UnitNumber: &unitNum,
|
|
||||||
}
|
|
||||||
deviceList = append(deviceList, dev)
|
|
||||||
}
|
|
||||||
return deviceList
|
|
||||||
}
|
|
||||||
@@ -1,472 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/property"
|
|
||||||
"github.com/vmware/govmomi/vim25"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VirtualMachine extends the govmomi VirtualMachine object
|
|
||||||
type VirtualMachine struct {
|
|
||||||
*object.VirtualMachine
|
|
||||||
Datacenter *Datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDiskAttached checks if disk is attached to the VM.
|
|
||||||
func (vm *VirtualMachine) IsDiskAttached(ctx context.Context, diskPath string) (bool, error) {
|
|
||||||
device, err := vm.getVirtualDeviceByPath(ctx, diskPath)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if device != nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteVM deletes the VM.
|
|
||||||
func (vm *VirtualMachine) DeleteVM(ctx context.Context) error {
|
|
||||||
destroyTask, err := vm.Destroy(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to delete the VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return destroyTask.Wait(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachDisk attaches the disk at location - vmDiskPath from Datastore - dsObj to the Virtual Machine
|
|
||||||
// Additionally the disk can be configured with SPBM policy if volumeOptions.StoragePolicyID is non-empty.
|
|
||||||
func (vm *VirtualMachine) AttachDisk(ctx context.Context, vmDiskPath string, volumeOptions *VolumeOptions) (string, error) {
|
|
||||||
// Check if the diskControllerType is valid
|
|
||||||
if !CheckControllerSupported(volumeOptions.SCSIControllerType) {
|
|
||||||
return "", fmt.Errorf("Not a valid SCSI Controller Type. Valid options are %q", SCSIControllerTypeValidOptions())
|
|
||||||
}
|
|
||||||
vmDiskPathCopy := vmDiskPath
|
|
||||||
vmDiskPath = RemoveStorageClusterORFolderNameFromVDiskPath(vmDiskPath)
|
|
||||||
attached, err := vm.IsDiskAttached(ctx, vmDiskPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while checking if disk is attached on VM: %q. vmDiskPath: %q, err: %+v", vm.InventoryPath, vmDiskPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// If disk is already attached, return the disk UUID
|
|
||||||
if attached {
|
|
||||||
diskUUID, _ := vm.Datacenter.GetVirtualDiskPage83Data(ctx, vmDiskPath)
|
|
||||||
return diskUUID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if volumeOptions.StoragePolicyName != "" {
|
|
||||||
pbmClient, err := NewPbmClient(ctx, vm.Client())
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while creating new pbmClient. err: %+v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeOptions.StoragePolicyID, err = pbmClient.ProfileIDByName(ctx, volumeOptions.StoragePolicyName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Profile ID by name: %s. err: %+v", volumeOptions.StoragePolicyName, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dsObj, err := vm.Datacenter.GetDatastoreByPath(ctx, vmDiskPathCopy)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get datastore from vmDiskPath: %q. err: %+v", vmDiskPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// If disk is not attached, create a disk spec for disk to be attached to the VM.
|
|
||||||
disk, newSCSIController, err := vm.CreateDiskSpec(ctx, vmDiskPath, dsObj, volumeOptions)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while creating disk spec. err: %+v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve VM devices for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
virtualMachineConfigSpec := types.VirtualMachineConfigSpec{}
|
|
||||||
deviceConfigSpec := &types.VirtualDeviceConfigSpec{
|
|
||||||
Device: disk,
|
|
||||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
|
||||||
}
|
|
||||||
// Configure the disk with the SPBM profile only if ProfileID is not empty.
|
|
||||||
if volumeOptions.StoragePolicyID != "" {
|
|
||||||
profileSpec := &types.VirtualMachineDefinedProfileSpec{
|
|
||||||
ProfileId: volumeOptions.StoragePolicyID,
|
|
||||||
}
|
|
||||||
deviceConfigSpec.Profile = append(deviceConfigSpec.Profile, profileSpec)
|
|
||||||
}
|
|
||||||
virtualMachineConfigSpec.DeviceChange = append(virtualMachineConfigSpec.DeviceChange, deviceConfigSpec)
|
|
||||||
requestTime := time.Now()
|
|
||||||
task, err := vm.Reconfigure(ctx, virtualMachineConfigSpec)
|
|
||||||
if err != nil {
|
|
||||||
RecordvSphereMetric(APIAttachVolume, requestTime, err)
|
|
||||||
klog.Errorf("Failed to attach the disk with storagePolicy: %q on VM: %q. err - %+v", volumeOptions.StoragePolicyID, vm.InventoryPath, err)
|
|
||||||
if newSCSIController != nil {
|
|
||||||
nestedErr := vm.deleteController(ctx, newSCSIController, vmDevices)
|
|
||||||
if nestedErr != nil {
|
|
||||||
return "", fmt.Errorf("failed to delete SCSI Controller after reconfiguration failed with err=%v: %v", err, nestedErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = task.Wait(ctx)
|
|
||||||
RecordvSphereMetric(APIAttachVolume, requestTime, err)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to attach the disk with storagePolicy: %+q on VM: %q. err - %+v", volumeOptions.StoragePolicyID, vm.InventoryPath, err)
|
|
||||||
if newSCSIController != nil {
|
|
||||||
nestedErr := vm.deleteController(ctx, newSCSIController, vmDevices)
|
|
||||||
if nestedErr != nil {
|
|
||||||
return "", fmt.Errorf("failed to delete SCSI Controller after waiting for reconfiguration failed with err='%v': %v", err, nestedErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once disk is attached, get the disk UUID.
|
|
||||||
diskUUID, err := vm.Datacenter.GetVirtualDiskPage83Data(ctx, vmDiskPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while getting Disk Info from VM: %q. err: %v", vm.InventoryPath, err)
|
|
||||||
nestedErr := vm.DetachDisk(ctx, vmDiskPath)
|
|
||||||
if nestedErr != nil {
|
|
||||||
return "", fmt.Errorf("failed to detach disk after getting VM UUID failed with err='%v': %v", err, nestedErr)
|
|
||||||
}
|
|
||||||
if newSCSIController != nil {
|
|
||||||
nestedErr = vm.deleteController(ctx, newSCSIController, vmDevices)
|
|
||||||
if nestedErr != nil {
|
|
||||||
return "", fmt.Errorf("failed to delete SCSI Controller after getting VM UUID failed with err='%v': %v", err, nestedErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return diskUUID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHost returns host of the virtual machine
|
|
||||||
func (vm *VirtualMachine) GetHost(ctx context.Context) (mo.HostSystem, error) {
|
|
||||||
host, err := vm.HostSystem(ctx)
|
|
||||||
var hostSystemMo mo.HostSystem
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get host system for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return hostSystemMo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := object.NewSearchIndex(vm.Client())
|
|
||||||
err = s.Properties(ctx, host.Reference(), []string{"summary"}, &hostSystemMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve datastores for host: %+v. err: %+v", host, err)
|
|
||||||
return hostSystemMo, err
|
|
||||||
}
|
|
||||||
return hostSystemMo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetachDisk detaches the disk specified by vmDiskPath
|
|
||||||
func (vm *VirtualMachine) DetachDisk(ctx context.Context, vmDiskPath string) error {
|
|
||||||
device, err := vm.getVirtualDeviceByPath(ctx, vmDiskPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Disk ID not found for VM: %q with diskPath: %q", vm.InventoryPath, vmDiskPath)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if device == nil {
|
|
||||||
klog.Errorf("No virtual device found with diskPath: %q on VM: %q", vmDiskPath, vm.InventoryPath)
|
|
||||||
return fmt.Errorf("No virtual device found with diskPath: %q on VM: %q", vmDiskPath, vm.InventoryPath)
|
|
||||||
}
|
|
||||||
// Detach disk from VM
|
|
||||||
requestTime := time.Now()
|
|
||||||
err = vm.RemoveDevice(ctx, true, device)
|
|
||||||
RecordvSphereMetric(APIDetachVolume, requestTime, err)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while removing disk device for VM: %q. err: %v", vm.InventoryPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResourcePool gets the resource pool for VM.
|
|
||||||
func (vm *VirtualMachine) GetResourcePool(ctx context.Context) (*object.ResourcePool, error) {
|
|
||||||
vmMoList, err := vm.Datacenter.GetVMMoList(ctx, []*VirtualMachine{vm}, []string{"resourcePool"})
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get resource pool from VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return object.NewResourcePool(vm.Client(), vmMoList[0].ResourcePool.Reference()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsActive checks if the VM is active.
|
|
||||||
// Returns true if VM is in poweredOn state.
|
|
||||||
func (vm *VirtualMachine) IsActive(ctx context.Context) (bool, error) {
|
|
||||||
vmMoList, err := vm.Datacenter.GetVMMoList(ctx, []*VirtualMachine{vm}, []string{"summary"})
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get VM Managed object with property summary. err: +%v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if vmMoList[0].Summary.Runtime.PowerState == ActivePowerState {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists checks if VM exists and is not terminated
|
|
||||||
func (vm *VirtualMachine) Exists(ctx context.Context) (bool, error) {
|
|
||||||
vmMoList, err := vm.Datacenter.GetVMMoList(ctx, []*VirtualMachine{vm}, []string{"summary.runtime.powerState"})
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get VM Managed object with property summary. err: +%v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
// We check for VMs which are still available in vcenter and has not been terminated/removed from
|
|
||||||
// disk and hence we consider PoweredOn,PoweredOff and Suspended as alive states.
|
|
||||||
aliveStates := []types.VirtualMachinePowerState{
|
|
||||||
types.VirtualMachinePowerStatePoweredOff,
|
|
||||||
types.VirtualMachinePowerStatePoweredOn,
|
|
||||||
types.VirtualMachinePowerStateSuspended,
|
|
||||||
}
|
|
||||||
currentState := vmMoList[0].Summary.Runtime.PowerState
|
|
||||||
for _, state := range aliveStates {
|
|
||||||
if state == currentState {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllAccessibleDatastores gets the list of accessible Datastores for the given Virtual Machine
|
|
||||||
func (vm *VirtualMachine) GetAllAccessibleDatastores(ctx context.Context) ([]*DatastoreInfo, error) {
|
|
||||||
host, err := vm.HostSystem(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get host system for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var hostSystemMo mo.HostSystem
|
|
||||||
s := object.NewSearchIndex(vm.Client())
|
|
||||||
err = s.Properties(ctx, host.Reference(), []string{DatastoreProperty}, &hostSystemMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve datastores for host: %+v. err: %+v", host, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var dsRefList []types.ManagedObjectReference
|
|
||||||
dsRefList = append(dsRefList, hostSystemMo.Datastore...)
|
|
||||||
|
|
||||||
var dsMoList []mo.Datastore
|
|
||||||
pc := property.DefaultCollector(vm.Client())
|
|
||||||
properties := []string{DatastoreInfoProperty, NameProperty}
|
|
||||||
err = pc.Retrieve(ctx, dsRefList, properties, &dsMoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Datastore managed objects from datastore objects."+
|
|
||||||
" dsObjList: %+v, properties: %+v, err: %v", dsRefList, properties, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("Result dsMoList: %+v", dsMoList)
|
|
||||||
finder := getFinder(vm.Datacenter)
|
|
||||||
var dsObjList []*DatastoreInfo
|
|
||||||
for _, dsMo := range dsMoList {
|
|
||||||
// use the finder so that InventoryPath is set correctly in ds
|
|
||||||
ds, err := finder.Datastore(ctx, dsMo.Name)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed finding datastore: %s. err: %+v", dsMo.Name, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datastore := Datastore{ds, vm.Datacenter}
|
|
||||||
dsObjList = append(dsObjList,
|
|
||||||
&DatastoreInfo{
|
|
||||||
&datastore,
|
|
||||||
dsMo.Info.GetDatastoreInfo()})
|
|
||||||
}
|
|
||||||
return dsObjList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDiskSpec creates a disk spec for disk
|
|
||||||
func (vm *VirtualMachine) CreateDiskSpec(ctx context.Context, diskPath string, dsObj *Datastore, volumeOptions *VolumeOptions) (*types.VirtualDisk, types.BaseVirtualDevice, error) {
|
|
||||||
var newSCSIController types.BaseVirtualDevice
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve VM devices. err: %+v", err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// find SCSI controller of particular type from VM devices
|
|
||||||
scsiControllersOfRequiredType := getSCSIControllersOfType(vmDevices, volumeOptions.SCSIControllerType)
|
|
||||||
scsiController := getAvailableSCSIController(scsiControllersOfRequiredType)
|
|
||||||
if scsiController == nil {
|
|
||||||
newSCSIController, err = vm.createAndAttachSCSIController(ctx, volumeOptions.SCSIControllerType)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create SCSI controller for VM :%q with err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Get VM device list
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve VM devices. err: %v", err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// verify scsi controller in virtual machine
|
|
||||||
scsiControllersOfRequiredType := getSCSIControllersOfType(vmDevices, volumeOptions.SCSIControllerType)
|
|
||||||
scsiController = getAvailableSCSIController(scsiControllersOfRequiredType)
|
|
||||||
if scsiController == nil {
|
|
||||||
klog.Errorf("Cannot find SCSI controller of type: %q in VM", volumeOptions.SCSIControllerType)
|
|
||||||
// attempt clean up of scsi controller
|
|
||||||
if err := vm.deleteController(ctx, newSCSIController, vmDevices); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to delete SCSI controller after failing to find it on VM: %v", err)
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("cannot find SCSI controller of type: %q in VM", volumeOptions.SCSIControllerType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
disk := vmDevices.CreateDisk(scsiController, dsObj.Reference(), diskPath)
|
|
||||||
unitNumber, err := getNextUnitNumber(vmDevices, scsiController)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Cannot attach disk to VM, unitNumber limit reached - %+v.", err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
*disk.UnitNumber = unitNumber
|
|
||||||
backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
|
|
||||||
backing.DiskMode = string(types.VirtualDiskModeIndependent_persistent)
|
|
||||||
|
|
||||||
if volumeOptions.CapacityKB != 0 {
|
|
||||||
disk.CapacityInKB = int64(volumeOptions.CapacityKB)
|
|
||||||
}
|
|
||||||
if volumeOptions.DiskFormat != "" {
|
|
||||||
var diskFormat string
|
|
||||||
diskFormat = DiskFormatValidType[volumeOptions.DiskFormat]
|
|
||||||
switch diskFormat {
|
|
||||||
case ThinDiskType:
|
|
||||||
backing.ThinProvisioned = types.NewBool(true)
|
|
||||||
case EagerZeroedThickDiskType:
|
|
||||||
backing.EagerlyScrub = types.NewBool(true)
|
|
||||||
default:
|
|
||||||
backing.ThinProvisioned = types.NewBool(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return disk, newSCSIController, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVirtualDiskPath gets the first available virtual disk devicePath from the VM
|
|
||||||
func (vm *VirtualMachine) GetVirtualDiskPath(ctx context.Context) (string, error) {
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get the devices for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// filter vm devices to retrieve device for the given vmdk file identified by disk path
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
if vmDevices.TypeName(device) == "VirtualDisk" {
|
|
||||||
virtualDevice := device.GetVirtualDevice()
|
|
||||||
if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
|
||||||
return backing.FileName, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createAndAttachSCSIController creates and attachs the SCSI controller to the VM.
|
|
||||||
func (vm *VirtualMachine) createAndAttachSCSIController(ctx context.Context, diskControllerType string) (types.BaseVirtualDevice, error) {
|
|
||||||
// Get VM device list
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to retrieve VM devices for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
allSCSIControllers := getSCSIControllers(vmDevices)
|
|
||||||
if len(allSCSIControllers) >= SCSIControllerLimit {
|
|
||||||
// we reached the maximum number of controllers we can attach
|
|
||||||
klog.Errorf("SCSI Controller Limit of %d has been reached, cannot create another SCSI controller", SCSIControllerLimit)
|
|
||||||
return nil, fmt.Errorf("SCSI Controller Limit of %d has been reached, cannot create another SCSI controller", SCSIControllerLimit)
|
|
||||||
}
|
|
||||||
newSCSIController, err := vmDevices.CreateSCSIController(diskControllerType)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create new SCSI controller on VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
configNewSCSIController := newSCSIController.(types.BaseVirtualSCSIController).GetVirtualSCSIController()
|
|
||||||
hotAndRemove := true
|
|
||||||
configNewSCSIController.HotAddRemove = &hotAndRemove
|
|
||||||
configNewSCSIController.SharedBus = types.VirtualSCSISharing(types.VirtualSCSISharingNoSharing)
|
|
||||||
|
|
||||||
// add the scsi controller to virtual machine
|
|
||||||
err = vm.AddDevice(context.TODO(), newSCSIController)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(LogLevel).Infof("Cannot add SCSI controller to VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
// attempt clean up of scsi controller
|
|
||||||
nestedErr := vm.deleteController(ctx, newSCSIController, vmDevices)
|
|
||||||
if nestedErr != nil {
|
|
||||||
return nil, fmt.Errorf("failed to delete SCSI controller after failing to add it to vm with err='%v': %v", err, nestedErr)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSCSIController, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getVirtualDeviceByPath gets the virtual device by path
|
|
||||||
func (vm *VirtualMachine) getVirtualDeviceByPath(ctx context.Context, diskPath string) (types.BaseVirtualDevice, error) {
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get the devices for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter vm devices to retrieve device for the given vmdk file identified by disk path
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
if vmDevices.TypeName(device) == "VirtualDisk" {
|
|
||||||
virtualDevice := device.GetVirtualDevice()
|
|
||||||
if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
|
||||||
if matchVirtualDiskAndVolPath(backing.FileName, diskPath) {
|
|
||||||
klog.V(LogLevel).Infof("Found VirtualDisk backing with filename %q for diskPath %q", backing.FileName, diskPath)
|
|
||||||
return device, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchVirtualDiskAndVolPath(diskPath, volPath string) bool {
|
|
||||||
fileExt := ".vmdk"
|
|
||||||
diskPath = strings.TrimSuffix(diskPath, fileExt)
|
|
||||||
volPath = strings.TrimSuffix(volPath, fileExt)
|
|
||||||
return diskPath == volPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteController removes latest added SCSI controller from VM.
|
|
||||||
func (vm *VirtualMachine) deleteController(ctx context.Context, controllerDevice types.BaseVirtualDevice, vmDevices object.VirtualDeviceList) error {
|
|
||||||
controllerDeviceList := vmDevices.SelectByType(controllerDevice)
|
|
||||||
if len(controllerDeviceList) < 1 {
|
|
||||||
return ErrNoDevicesFound
|
|
||||||
}
|
|
||||||
device := controllerDeviceList[len(controllerDeviceList)-1]
|
|
||||||
err := vm.RemoveDevice(ctx, true, device)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error occurred while removing device on VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewVM renews this virtual machine with new client connection.
|
|
||||||
func (vm *VirtualMachine) RenewVM(client *vim25.Client) VirtualMachine {
|
|
||||||
dc := Datacenter{Datacenter: object.NewDatacenter(client, vm.Datacenter.Reference())}
|
|
||||||
newVM := object.NewVirtualMachine(client, vm.VirtualMachine.Reference())
|
|
||||||
return VirtualMachine{VirtualMachine: newVM, Datacenter: &dc}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/simulator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVirtualMachine(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
model := simulator.VPX()
|
|
||||||
|
|
||||||
defer model.Remove()
|
|
||||||
err := model.Create()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := model.Service.NewServer()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
c, err := govmomi.NewClient(ctx, s.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vc := &VSphereConnection{Client: c.Client}
|
|
||||||
|
|
||||||
dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
folders, err := dc.Folders(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
folder, err := dc.GetFolderByPath(ctx, folders.VmFolder.InventoryPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vms, err := folder.GetVirtualMachines(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vms) == 0 {
|
|
||||||
t.Fatal("no VMs")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vm := range vms {
|
|
||||||
all, err := vm.GetAllAccessibleDatastores(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if len(all) == 0 {
|
|
||||||
t.Error("no accessible datastores")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = vm.GetResourcePool(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
diskPath, err := vm.GetVirtualDiskPath(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := &VolumeOptions{SCSIControllerType: PVSCSIControllerType}
|
|
||||||
|
|
||||||
for _, expect := range []bool{true, false} {
|
|
||||||
attached, err := vm.IsDiskAttached(ctx, diskPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if attached != expect {
|
|
||||||
t.Errorf("attached=%t, expected=%t", attached, expect)
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid, err := vm.AttachDisk(ctx, diskPath, options)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if uuid == "" {
|
|
||||||
t.Error("missing uuid")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = vm.DetachDisk(ctx, diskPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, turnOff := range []bool{true, false} {
|
|
||||||
// Turn off for checking if exist return true
|
|
||||||
if turnOff {
|
|
||||||
_, _ = vm.PowerOff(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
exist, err := vm.Exists(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
t.Errorf("exist=%t, expected=%t", exist, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn back on
|
|
||||||
if turnOff {
|
|
||||||
_, _ = vm.PowerOn(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, expect := range []bool{true, false} {
|
|
||||||
active, err := vm.IsActive(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if active != expect {
|
|
||||||
t.Errorf("active=%t, expected=%t", active, expect)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expect {
|
|
||||||
// Expecting to hit the error path since the VM is still powered on
|
|
||||||
err = vm.DeleteVM(ctx)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
_, _ = vm.PowerOff(ctx)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be able to delete now that VM power is off
|
|
||||||
err = vm.DeleteVM(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expecting Exists func to throw error if VM deleted
|
|
||||||
_, err = vm.Exists(ctx)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VMOptions provides helper objects for provisioning volume with SPBM Policy
|
|
||||||
type VMOptions struct {
|
|
||||||
VMFolder *Folder
|
|
||||||
VMResourcePool *object.ResourcePool
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VolumeOptions specifies various options for a volume.
|
|
||||||
type VolumeOptions struct {
|
|
||||||
CapacityKB int
|
|
||||||
Tags map[string]string
|
|
||||||
Name string
|
|
||||||
DiskFormat string
|
|
||||||
Datastore string
|
|
||||||
VSANStorageProfileData string
|
|
||||||
StoragePolicyName string
|
|
||||||
StoragePolicyID string
|
|
||||||
SCSIControllerType string
|
|
||||||
Zone []string
|
|
||||||
SelectedNode *v1.Node
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DiskFormatValidType specifies the valid disk formats
|
|
||||||
DiskFormatValidType = map[string]string{
|
|
||||||
ThinDiskType: ThinDiskType,
|
|
||||||
strings.ToLower(EagerZeroedThickDiskType): EagerZeroedThickDiskType,
|
|
||||||
strings.ToLower(ZeroedThickDiskType): PreallocatedDiskType,
|
|
||||||
}
|
|
||||||
// SCSIControllerValidType specifies the supported SCSI controllers
|
|
||||||
SCSIControllerValidType = []string{LSILogicControllerType, LSILogicSASControllerType, PVSCSIControllerType}
|
|
||||||
)
|
|
||||||
|
|
||||||
// DiskformatValidOptions generates Valid Options for Diskformat
|
|
||||||
func DiskformatValidOptions() string {
|
|
||||||
validopts := ""
|
|
||||||
for diskformat := range DiskFormatValidType {
|
|
||||||
validopts += diskformat + ", "
|
|
||||||
}
|
|
||||||
validopts = strings.TrimSuffix(validopts, ", ")
|
|
||||||
return validopts
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDiskFormatSupported checks if the diskFormat is valid
|
|
||||||
func CheckDiskFormatSupported(diskFormat string) bool {
|
|
||||||
if DiskFormatValidType[diskFormat] == "" {
|
|
||||||
klog.Errorf("Not a valid Disk Format. Valid options are %+q", DiskformatValidOptions())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SCSIControllerTypeValidOptions generates valid options for SCSIControllerType
|
|
||||||
func SCSIControllerTypeValidOptions() string {
|
|
||||||
validopts := ""
|
|
||||||
for _, controllerType := range SCSIControllerValidType {
|
|
||||||
validopts += (controllerType + ", ")
|
|
||||||
}
|
|
||||||
validopts = strings.TrimSuffix(validopts, ", ")
|
|
||||||
return validopts
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckControllerSupported checks if the given controller type is valid
|
|
||||||
func CheckControllerSupported(ctrlType string) bool {
|
|
||||||
for _, c := range SCSIControllerValidType {
|
|
||||||
if ctrlType == c {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.Errorf("Not a valid SCSI Controller Type. Valid options are %q", SCSIControllerTypeValidOptions())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyVolumeOptions checks if volumeOptions.SCIControllerType is valid controller type
|
|
||||||
func (volumeOptions VolumeOptions) VerifyVolumeOptions() error {
|
|
||||||
// Validate only if SCSIControllerType is set by user.
|
|
||||||
// Default value is set later in virtualDiskManager.Create and vmDiskManager.Create
|
|
||||||
if volumeOptions.SCSIControllerType != "" {
|
|
||||||
isValid := CheckControllerSupported(volumeOptions.SCSIControllerType)
|
|
||||||
if !isValid {
|
|
||||||
return fmt.Errorf("invalid scsiControllerType: %s", volumeOptions.SCSIControllerType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ThinDiskType is the default, so skip the validation.
|
|
||||||
if volumeOptions.DiskFormat != ThinDiskType {
|
|
||||||
isValid := CheckDiskFormatSupported(volumeOptions.DiskFormat)
|
|
||||||
if !isValid {
|
|
||||||
return fmt.Errorf("invalid diskFormat: %s", volumeOptions.DiskFormat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vclib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/component-base/metrics"
|
|
||||||
"k8s.io/component-base/metrics/legacyregistry"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cloud Provider API constants
|
|
||||||
const (
|
|
||||||
APICreateVolume = "CreateVolume"
|
|
||||||
APIDeleteVolume = "DeleteVolume"
|
|
||||||
APIAttachVolume = "AttachVolume"
|
|
||||||
APIDetachVolume = "DetachVolume"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cloud Provider Operation constants
|
|
||||||
const (
|
|
||||||
OperationDeleteVolume = "DeleteVolumeOperation"
|
|
||||||
OperationAttachVolume = "AttachVolumeOperation"
|
|
||||||
OperationDetachVolume = "DetachVolumeOperation"
|
|
||||||
OperationDiskIsAttached = "DiskIsAttachedOperation"
|
|
||||||
OperationDisksAreAttached = "DisksAreAttachedOperation"
|
|
||||||
OperationCreateVolume = "CreateVolumeOperation"
|
|
||||||
OperationCreateVolumeWithPolicy = "CreateVolumeWithPolicyOperation"
|
|
||||||
OperationCreateVolumeWithRawVSANPolicy = "CreateVolumeWithRawVSANPolicyOperation"
|
|
||||||
)
|
|
||||||
|
|
||||||
var vCenterMetric *vcenterMetric
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
vCenterMetric = &vcenterMetric{
|
|
||||||
vCenterInfos: make(map[string]types.AboutInfo),
|
|
||||||
mux: sync.Mutex{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vsphereAPIMetric is for recording latency of Single API Call.
|
|
||||||
var vsphereAPIMetric = metrics.NewHistogramVec(
|
|
||||||
&metrics.HistogramOpts{
|
|
||||||
Name: "cloudprovider_vsphere_api_request_duration_seconds",
|
|
||||||
Help: "Latency of vsphere api call",
|
|
||||||
StabilityLevel: metrics.ALPHA,
|
|
||||||
},
|
|
||||||
[]string{"request"},
|
|
||||||
)
|
|
||||||
|
|
||||||
var vsphereAPIErrorMetric = metrics.NewCounterVec(
|
|
||||||
&metrics.CounterOpts{
|
|
||||||
Name: "cloudprovider_vsphere_api_request_errors",
|
|
||||||
Help: "vsphere Api errors",
|
|
||||||
StabilityLevel: metrics.ALPHA,
|
|
||||||
},
|
|
||||||
[]string{"request"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// vsphereOperationMetric is for recording latency of vSphere Operation which invokes multiple APIs to get the task done.
|
|
||||||
var vsphereOperationMetric = metrics.NewHistogramVec(
|
|
||||||
&metrics.HistogramOpts{
|
|
||||||
Name: "cloudprovider_vsphere_operation_duration_seconds",
|
|
||||||
Help: "Latency of vsphere operation call",
|
|
||||||
StabilityLevel: metrics.ALPHA,
|
|
||||||
},
|
|
||||||
[]string{"operation"},
|
|
||||||
)
|
|
||||||
|
|
||||||
var vsphereOperationErrorMetric = metrics.NewCounterVec(
|
|
||||||
&metrics.CounterOpts{
|
|
||||||
Name: "cloudprovider_vsphere_operation_errors",
|
|
||||||
Help: "vsphere operation errors",
|
|
||||||
StabilityLevel: metrics.ALPHA,
|
|
||||||
},
|
|
||||||
[]string{"operation"},
|
|
||||||
)
|
|
||||||
|
|
||||||
var vsphereVersion = metrics.NewDesc(
|
|
||||||
"cloudprovider_vsphere_vcenter_versions",
|
|
||||||
"Versions for connected vSphere vCenters",
|
|
||||||
[]string{"hostname", "version", "build"}, nil,
|
|
||||||
metrics.ALPHA, "")
|
|
||||||
|
|
||||||
// RegisterMetrics registers all the API and Operation metrics
|
|
||||||
func RegisterMetrics() {
|
|
||||||
legacyregistry.MustRegister(vsphereAPIMetric)
|
|
||||||
legacyregistry.MustRegister(vsphereAPIErrorMetric)
|
|
||||||
legacyregistry.MustRegister(vsphereOperationMetric)
|
|
||||||
legacyregistry.MustRegister(vsphereOperationErrorMetric)
|
|
||||||
legacyregistry.CustomMustRegister(vCenterMetric)
|
|
||||||
}
|
|
||||||
|
|
||||||
type vcenterMetric struct {
|
|
||||||
metrics.BaseStableCollector
|
|
||||||
|
|
||||||
mux sync.Mutex
|
|
||||||
vCenterInfos map[string]types.AboutInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collector *vcenterMetric) DescribeWithStability(ch chan<- *metrics.Desc) {
|
|
||||||
ch <- vsphereVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collector *vcenterMetric) CollectWithStability(ch chan<- metrics.Metric) {
|
|
||||||
collector.mux.Lock()
|
|
||||||
defer collector.mux.Unlock()
|
|
||||||
|
|
||||||
for vCenter, info := range collector.vCenterInfos {
|
|
||||||
ch <- metrics.NewLazyMetricWithTimestamp(time.Now(),
|
|
||||||
metrics.NewLazyConstMetric(vsphereVersion,
|
|
||||||
metrics.GaugeValue,
|
|
||||||
float64(1),
|
|
||||||
vCenter,
|
|
||||||
info.Version,
|
|
||||||
info.Build))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (collector *vcenterMetric) setAbout(server string, info types.AboutInfo) {
|
|
||||||
collector.mux.Lock()
|
|
||||||
defer collector.mux.Unlock()
|
|
||||||
collector.vCenterInfos[server] = info
|
|
||||||
}
|
|
||||||
|
|
||||||
func setVCenterInfoMetric(connection *VSphereConnection) {
|
|
||||||
vCenterMetric.setAbout(connection.Hostname, connection.Client.ServiceContent.About)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordvSphereMetric records the vSphere API and Operation metrics
|
|
||||||
func RecordvSphereMetric(actionName string, requestTime time.Time, err error) {
|
|
||||||
switch actionName {
|
|
||||||
case APICreateVolume, APIDeleteVolume, APIAttachVolume, APIDetachVolume:
|
|
||||||
recordvSphereAPIMetric(actionName, requestTime, err)
|
|
||||||
default:
|
|
||||||
recordvSphereOperationMetric(actionName, requestTime, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func recordvSphereAPIMetric(actionName string, requestTime time.Time, err error) {
|
|
||||||
if err != nil {
|
|
||||||
vsphereAPIErrorMetric.With(metrics.Labels{"request": actionName}).Inc()
|
|
||||||
} else {
|
|
||||||
vsphereAPIMetric.With(metrics.Labels{"request": actionName}).Observe(calculateTimeTaken(requestTime))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func recordvSphereOperationMetric(actionName string, requestTime time.Time, err error) {
|
|
||||||
if err != nil {
|
|
||||||
vsphereOperationErrorMetric.With(metrics.Labels{"operation": actionName}).Inc()
|
|
||||||
} else {
|
|
||||||
vsphereOperationMetric.With(metrics.Labels{"operation": actionName}).Observe(calculateTimeTaken(requestTime))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordCreateVolumeMetric records the Create Volume metric
|
|
||||||
func RecordCreateVolumeMetric(volumeOptions *VolumeOptions, requestTime time.Time, err error) {
|
|
||||||
var actionName string
|
|
||||||
if volumeOptions.StoragePolicyName != "" {
|
|
||||||
actionName = OperationCreateVolumeWithPolicy
|
|
||||||
} else if volumeOptions.VSANStorageProfileData != "" {
|
|
||||||
actionName = OperationCreateVolumeWithRawVSANPolicy
|
|
||||||
} else {
|
|
||||||
actionName = OperationCreateVolume
|
|
||||||
}
|
|
||||||
RecordvSphereMetric(actionName, requestTime, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateTimeTaken(requestBeginTime time.Time) (timeTaken float64) {
|
|
||||||
if !requestBeginTime.IsZero() {
|
|
||||||
timeTaken = time.Since(requestBeginTime).Seconds()
|
|
||||||
} else {
|
|
||||||
timeTaken = 0
|
|
||||||
}
|
|
||||||
return timeTaken
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,880 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/find"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/property"
|
|
||||||
"github.com/vmware/govmomi/vim25"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib/diskmanagers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DatastoreProperty = "datastore"
|
|
||||||
DatastoreInfoProperty = "info"
|
|
||||||
DatastoreNameProperty = "name"
|
|
||||||
Folder = "Folder"
|
|
||||||
VirtualMachine = "VirtualMachine"
|
|
||||||
DummyDiskName = "kube-dummyDisk.vmdk"
|
|
||||||
ProviderPrefix = "vsphere://"
|
|
||||||
vSphereConfFileEnvVar = "VSPHERE_CONF_FILE"
|
|
||||||
UUIDPrefix = "VMware-"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetVSphere reads vSphere configuration from system environment and construct vSphere object
|
|
||||||
func GetVSphere() (*VSphere, error) {
|
|
||||||
cfg, err := getVSphereConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
vs, err := newControllerNode(*cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return vs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVSphereConfig() (*VSphereConfig, error) {
|
|
||||||
confFileLocation := os.Getenv(vSphereConfFileEnvVar)
|
|
||||||
if confFileLocation == "" {
|
|
||||||
return nil, fmt.Errorf("Env variable 'VSPHERE_CONF_FILE' is not set.")
|
|
||||||
}
|
|
||||||
confFile, err := os.Open(confFileLocation)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := confFile.Close(); err != nil {
|
|
||||||
klog.Errorf("failed to close config file: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
cfg, err := readConfig(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the accessible datastores for the given node VM.
|
|
||||||
func getAccessibleDatastores(ctx context.Context, nodeVmDetail *NodeDetails, nodeManager *NodeManager) ([]*vclib.DatastoreInfo, error) {
|
|
||||||
accessibleDatastores, err := nodeVmDetail.vm.GetAllAccessibleDatastores(ctx)
|
|
||||||
if err != nil {
|
|
||||||
// Check if the node VM is not found which indicates that the node info in the node manager is stale.
|
|
||||||
// If so, rediscover the node and retry.
|
|
||||||
if vclib.IsManagedObjectNotFoundError(err) {
|
|
||||||
klog.V(4).Infof("error %q ManagedObjectNotFound for node %q. Rediscovering...", err, nodeVmDetail.NodeName)
|
|
||||||
err = nodeManager.RediscoverNode(convertToK8sType(nodeVmDetail.NodeName))
|
|
||||||
if err == nil {
|
|
||||||
klog.V(4).Infof("Discovered node %s successfully", nodeVmDetail.NodeName)
|
|
||||||
nodeInfo, err := nodeManager.GetNodeInfo(convertToK8sType(nodeVmDetail.NodeName))
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("error %q getting node info for node %+v", err, nodeVmDetail)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessibleDatastores, err = nodeInfo.vm.GetAllAccessibleDatastores(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("error %q getting accessible datastores for node %+v", err, nodeVmDetail)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
klog.V(4).Infof("error %q rediscovering node %+v", err, nodeVmDetail)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
klog.V(4).Infof("error %q getting accessible datastores for node %+v", err, nodeVmDetail)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accessibleDatastores, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all datastores accessible for the virtual machine object.
|
|
||||||
func getSharedDatastoresInK8SCluster(ctx context.Context, nodeManager *NodeManager) ([]*vclib.DatastoreInfo, error) {
|
|
||||||
nodeVmDetails, err := nodeManager.GetNodeDetails()
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error while obtaining Kubernetes node nodeVmDetail details. error : %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodeVmDetails) == 0 {
|
|
||||||
msg := fmt.Sprintf("Kubernetes node nodeVmDetail details is empty. nodeVmDetails : %+v", nodeVmDetails)
|
|
||||||
klog.Error(msg)
|
|
||||||
return nil, fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
var sharedDatastores []*vclib.DatastoreInfo
|
|
||||||
for _, nodeVmDetail := range nodeVmDetails {
|
|
||||||
klog.V(9).Infof("Getting accessible datastores for node %s", nodeVmDetail.NodeName)
|
|
||||||
accessibleDatastores, err := getAccessibleDatastores(ctx, &nodeVmDetail, nodeManager)
|
|
||||||
if err != nil {
|
|
||||||
if err == vclib.ErrNoVMFound {
|
|
||||||
klog.V(9).Infof("Got NoVMFound error for node %s", nodeVmDetail.NodeName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sharedDatastores) == 0 {
|
|
||||||
sharedDatastores = accessibleDatastores
|
|
||||||
} else {
|
|
||||||
sharedDatastores = intersect(sharedDatastores, accessibleDatastores)
|
|
||||||
if len(sharedDatastores) == 0 {
|
|
||||||
return nil, fmt.Errorf("No shared datastores found in the Kubernetes cluster for nodeVmDetails: %+v", nodeVmDetails)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("sharedDatastores : %+v", sharedDatastores)
|
|
||||||
return sharedDatastores, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func intersect(list1 []*vclib.DatastoreInfo, list2 []*vclib.DatastoreInfo) []*vclib.DatastoreInfo {
|
|
||||||
klog.V(9).Infof("list1: %+v", list1)
|
|
||||||
klog.V(9).Infof("list2: %+v", list2)
|
|
||||||
var sharedDs []*vclib.DatastoreInfo
|
|
||||||
for _, val1 := range list1 {
|
|
||||||
// Check if val1 is found in list2
|
|
||||||
for _, val2 := range list2 {
|
|
||||||
// Intersection is performed based on the datastoreUrl as this uniquely identifies the datastore.
|
|
||||||
if val1.Info.Url == val2.Info.Url {
|
|
||||||
sharedDs = append(sharedDs, val1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sharedDs
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMostFreeDatastore gets the best fit compatible datastore by free space.
|
|
||||||
func getMostFreeDatastore(ctx context.Context, client *vim25.Client, dsInfoList []*vclib.DatastoreInfo) (*vclib.DatastoreInfo, error) {
|
|
||||||
var curMax int64
|
|
||||||
curMax = -1
|
|
||||||
var index int
|
|
||||||
for i, dsInfo := range dsInfoList {
|
|
||||||
dsFreeSpace := dsInfo.Info.GetDatastoreInfo().FreeSpace
|
|
||||||
if dsFreeSpace > curMax {
|
|
||||||
curMax = dsFreeSpace
|
|
||||||
index = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dsInfoList[index], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPbmCompatibleDatastore(ctx context.Context, vcClient *vim25.Client, storagePolicyName string, nodeManager *NodeManager) (*vclib.DatastoreInfo, error) {
|
|
||||||
pbmClient, err := vclib.NewPbmClient(ctx, vcClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
storagePolicyID, err := pbmClient.ProfileIDByName(ctx, storagePolicyName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Profile ID by name: %s. err: %+v", storagePolicyName, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sharedDs, err := getSharedDatastoresInK8SCluster(ctx, nodeManager)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get shared datastores. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(sharedDs) == 0 {
|
|
||||||
msg := "No shared datastores found in the endpoint virtual center"
|
|
||||||
klog.Errorf(msg)
|
|
||||||
return nil, errors.New(msg)
|
|
||||||
}
|
|
||||||
compatibleDatastores, _, err := pbmClient.GetCompatibleDatastores(ctx, storagePolicyID, sharedDs)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get compatible datastores from datastores : %+v with storagePolicy: %s. err: %+v",
|
|
||||||
sharedDs, storagePolicyID, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("compatibleDatastores : %+v", compatibleDatastores)
|
|
||||||
datastore, err := getMostFreeDatastore(ctx, vcClient, compatibleDatastores)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get most free datastore from compatible datastores: %+v. err: %+v", compatibleDatastores, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Most free datastore : %+s", datastore.Info.Name)
|
|
||||||
return datastore, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDatastoresForZone(ctx context.Context, nodeManager *NodeManager, selectedZones []string) ([]*vclib.DatastoreInfo, error) {
|
|
||||||
|
|
||||||
var sharedDatastores []*vclib.DatastoreInfo
|
|
||||||
|
|
||||||
for _, zone := range selectedZones {
|
|
||||||
var sharedDatastoresPerZone []*vclib.DatastoreInfo
|
|
||||||
hosts, err := nodeManager.GetHostsInZone(ctx, zone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Hosts in zone %s : %s", zone, hosts)
|
|
||||||
|
|
||||||
for _, host := range hosts {
|
|
||||||
var hostSystemMo mo.HostSystem
|
|
||||||
err = host.Properties(ctx, host.Reference(), []string{"datastore"}, &hostSystemMo)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get datastore property for host %s. err : %+v", host, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(4).Infof("Datastores mounted on host %s : %s", host, hostSystemMo.Datastore)
|
|
||||||
var dsRefList []types.ManagedObjectReference
|
|
||||||
for _, dsRef := range hostSystemMo.Datastore {
|
|
||||||
dsRefList = append(dsRefList, dsRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
var dsMoList []mo.Datastore
|
|
||||||
pc := property.DefaultCollector(host.Client())
|
|
||||||
properties := []string{DatastoreInfoProperty, DatastoreNameProperty}
|
|
||||||
err = pc.Retrieve(ctx, dsRefList, properties, &dsMoList)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Datastore managed objects from datastore objects."+
|
|
||||||
" dsObjList: %+v, properties: %+v, err: %+v", dsRefList, properties, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("Datastore mo details: %+v", dsMoList)
|
|
||||||
|
|
||||||
// find the Datacenter parent for this host
|
|
||||||
mes, err := mo.Ancestors(ctx, host.Client(), pc.Reference(), host.Reference())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var dcMoref *types.ManagedObjectReference
|
|
||||||
for i := len(mes) - 1; i > 0; i-- {
|
|
||||||
if mes[i].Self.Type == "Datacenter" {
|
|
||||||
dcMoref = &mes[i].Self
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dcMoref == nil {
|
|
||||||
return nil, fmt.Errorf("failed to find the Datacenter of host %s", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
dc := object.NewDatacenter(host.Client(), *dcMoref)
|
|
||||||
finder := find.NewFinder(host.Client(), false)
|
|
||||||
finder.SetDatacenter(dc)
|
|
||||||
var dsObjList []*vclib.DatastoreInfo
|
|
||||||
for _, dsMo := range dsMoList {
|
|
||||||
// use the finder so that InventoryPath is set correctly in dsObj
|
|
||||||
dsObj, err := finder.Datastore(ctx, dsMo.Name)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to find datastore named %s in datacenter %s", dsMo.Name, dc)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dsObjList = append(dsObjList,
|
|
||||||
&vclib.DatastoreInfo{
|
|
||||||
Datastore: &vclib.Datastore{Datastore: dsObj,
|
|
||||||
Datacenter: &vclib.Datacenter{Datacenter: dc}},
|
|
||||||
Info: dsMo.Info.GetDatastoreInfo()})
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(9).Infof("DatastoreInfo details : %s", dsObjList)
|
|
||||||
|
|
||||||
if len(sharedDatastoresPerZone) == 0 {
|
|
||||||
sharedDatastoresPerZone = dsObjList
|
|
||||||
} else {
|
|
||||||
sharedDatastoresPerZone = intersect(sharedDatastoresPerZone, dsObjList)
|
|
||||||
if len(sharedDatastoresPerZone) == 0 {
|
|
||||||
klog.V(4).Infof("No shared datastores found among hosts %s", hosts)
|
|
||||||
return nil, fmt.Errorf("No matching datastores found in the kubernetes cluster for zone %s", zone)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("Shared datastore list after processing host %s : %s", host, sharedDatastoresPerZone)
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Shared datastore per zone %s is %s", zone, sharedDatastoresPerZone)
|
|
||||||
if len(sharedDatastores) == 0 {
|
|
||||||
sharedDatastores = sharedDatastoresPerZone
|
|
||||||
} else {
|
|
||||||
sharedDatastores = intersect(sharedDatastores, sharedDatastoresPerZone)
|
|
||||||
if len(sharedDatastores) == 0 {
|
|
||||||
return nil, fmt.Errorf("No matching datastores found in the kubernetes cluster across zones %s", selectedZones)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.V(1).Infof("Returning selected datastores : %s", sharedDatastores)
|
|
||||||
return sharedDatastores, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPbmCompatibleZonedDatastore(ctx context.Context, vcClient *vim25.Client, storagePolicyName string, zonedDatastores []*vclib.DatastoreInfo) (*vclib.DatastoreInfo, error) {
|
|
||||||
pbmClient, err := vclib.NewPbmClient(ctx, vcClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
storagePolicyID, err := pbmClient.ProfileIDByName(ctx, storagePolicyName)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get Profile ID by name: %s. err: %+v", storagePolicyName, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
compatibleDatastores, _, err := pbmClient.GetCompatibleDatastores(ctx, storagePolicyID, zonedDatastores)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get compatible datastores from datastores : %+v with storagePolicy: %s. err: %+v",
|
|
||||||
zonedDatastores, storagePolicyID, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("compatibleDatastores : %+v", compatibleDatastores)
|
|
||||||
datastore, err := getMostFreeDatastore(ctx, vcClient, compatibleDatastores)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get most free datastore from compatible datastores: %+v. err: %+v", compatibleDatastores, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Most free datastore : %+s", datastore.Info.Name)
|
|
||||||
return datastore, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vs *VSphere) setVMOptions(ctx context.Context, connection *vclib.VSphereConnection, ds *vclib.Datastore) (*vclib.VMOptions, error) {
|
|
||||||
var vmOptions vclib.VMOptions
|
|
||||||
dsHosts, err := ds.GetDatastoreHostMounts(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get datastore host mounts for %v: %+v", ds, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// pick a host at random to use for Volume creation
|
|
||||||
dsHostMoref := dsHosts[rand.New(rand.NewSource(time.Now().UnixNano())).Intn(len(dsHosts))]
|
|
||||||
dummyVMHost := object.NewHostSystem(connection.Client, dsHostMoref)
|
|
||||||
resourcePool, err := dummyVMHost.ResourcePool(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get resource pool from host %v", dummyVMHost)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
folder, err := ds.Datacenter.GetFolderByPath(ctx, vs.cfg.Workspace.Folder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
vmOptions.VMFolder = folder
|
|
||||||
vmOptions.VMResourcePool = resourcePool
|
|
||||||
return &vmOptions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// A background routine which will be responsible for deleting stale dummy VM's.
|
|
||||||
func (vs *VSphere) cleanUpDummyVMs(dummyVMPrefix string) {
|
|
||||||
// Create context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
for {
|
|
||||||
time.Sleep(CleanUpDummyVMRoutineInterval * time.Minute)
|
|
||||||
datacenters, err := vs.GetWorkspaceDatacenters(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Failed to get datacenters from VC. err: %+v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Clean up dummy VMs in each datacenter
|
|
||||||
for _, dc := range datacenters {
|
|
||||||
// Get the folder reference for global working directory where the dummy VM needs to be created.
|
|
||||||
vmFolder, err := dc.GetFolderByPath(ctx, vs.cfg.Workspace.Folder)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Unable to get the kubernetes folder: %q reference. err: %+v", vs.cfg.Workspace.Folder, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// A write lock is acquired to make sure the cleanUp routine doesn't delete any VM's created by ongoing PVC requests.
|
|
||||||
cleanUpDummyVMs := func() {
|
|
||||||
cleanUpDummyVMLock.Lock()
|
|
||||||
defer cleanUpDummyVMLock.Unlock()
|
|
||||||
err = diskmanagers.CleanUpDummyVMs(ctx, vmFolder)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Unable to clean up dummy VM's in the kubernetes cluster: %q. err: %+v", vs.cfg.Workspace.Folder, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleanUpDummyVMs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get canonical volume path for volume Path.
|
|
||||||
// Example1: The canonical path for volume path - [vsanDatastore] kubevols/volume.vmdk will be [vsanDatastore] 25d8b159-948c-4b73-e499-02001ad1b044/volume.vmdk
|
|
||||||
// Example2: The canonical path for volume path - [vsanDatastore] 25d8b159-948c-4b73-e499-02001ad1b044/volume.vmdk will be same as volume Path.
|
|
||||||
func getcanonicalVolumePath(ctx context.Context, dc *vclib.Datacenter, volumePath string) (string, error) {
|
|
||||||
var folderID string
|
|
||||||
var folderExists bool
|
|
||||||
canonicalVolumePath := volumePath
|
|
||||||
dsPathObj, err := vclib.GetDatastorePathObjFromVMDiskPath(volumePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
dsPath := strings.Split(strings.TrimSpace(dsPathObj.Path), "/")
|
|
||||||
if len(dsPath) <= 1 {
|
|
||||||
return canonicalVolumePath, nil
|
|
||||||
}
|
|
||||||
datastore := dsPathObj.Datastore
|
|
||||||
dsFolder := dsPath[0]
|
|
||||||
folderNameIDMap, datastoreExists := datastoreFolderIDMap[datastore]
|
|
||||||
if datastoreExists {
|
|
||||||
folderID, folderExists = folderNameIDMap[dsFolder]
|
|
||||||
}
|
|
||||||
// Get the datastore folder ID if datastore or folder doesn't exist in datastoreFolderIDMap
|
|
||||||
if !datastoreExists || !folderExists {
|
|
||||||
if !vclib.IsValidUUID(dsFolder) {
|
|
||||||
dummyDiskVolPath := "[" + datastore + "] " + dsFolder + "/" + DummyDiskName
|
|
||||||
// Querying a non-existent dummy disk on the datastore folder.
|
|
||||||
// It would fail and return an folder ID in the error message.
|
|
||||||
_, err := dc.GetVirtualDiskPage83Data(ctx, dummyDiskVolPath)
|
|
||||||
canonicalVolumePath, err = getPathFromFileNotFound(err)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to get path from dummy request: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diskPath := vclib.GetPathFromVMDiskPath(canonicalVolumePath)
|
|
||||||
if diskPath == "" {
|
|
||||||
return "", fmt.Errorf("Failed to parse canonicalVolumePath: %s in getcanonicalVolumePath method", canonicalVolumePath)
|
|
||||||
}
|
|
||||||
folderID = strings.Split(strings.TrimSpace(diskPath), "/")[0]
|
|
||||||
setdatastoreFolderIDMap(datastoreFolderIDMap, datastore, dsFolder, folderID)
|
|
||||||
}
|
|
||||||
canonicalVolumePath = strings.Replace(volumePath, dsFolder, folderID, 1)
|
|
||||||
return canonicalVolumePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPathFromFileNotFound returns the path from a fileNotFound error
|
|
||||||
func getPathFromFileNotFound(err error) (string, error) {
|
|
||||||
if soap.IsSoapFault(err) {
|
|
||||||
fault := soap.ToSoapFault(err)
|
|
||||||
f, ok := fault.VimFault().(types.FileNotFound)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%v is not a FileNotFound error", err)
|
|
||||||
}
|
|
||||||
return f.File, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("%v is not a soap fault", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setdatastoreFolderIDMap(
|
|
||||||
datastoreFolderIDMap map[string]map[string]string,
|
|
||||||
datastore string,
|
|
||||||
folderName string,
|
|
||||||
folderID string) {
|
|
||||||
folderNameIDMap := datastoreFolderIDMap[datastore]
|
|
||||||
if folderNameIDMap == nil {
|
|
||||||
folderNameIDMap = make(map[string]string)
|
|
||||||
datastoreFolderIDMap[datastore] = folderNameIDMap
|
|
||||||
}
|
|
||||||
folderNameIDMap[folderName] = folderID
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertVolPathToDevicePath(ctx context.Context, dc *vclib.Datacenter, volPath string) (string, error) {
|
|
||||||
volPath = vclib.RemoveStorageClusterORFolderNameFromVDiskPath(volPath)
|
|
||||||
// Get the canonical volume path for volPath.
|
|
||||||
canonicalVolumePath, err := getcanonicalVolumePath(ctx, dc, volPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get canonical vsphere volume path for volume: %s. err: %+v", volPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Check if the volume path contains .vmdk extension. If not, add the extension and update the nodeVolumes Map
|
|
||||||
if len(canonicalVolumePath) > 0 && filepath.Ext(canonicalVolumePath) != ".vmdk" {
|
|
||||||
canonicalVolumePath += ".vmdk"
|
|
||||||
}
|
|
||||||
return canonicalVolumePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertVolPathsToDevicePaths removes cluster or folder path from volPaths and convert to canonicalPath
|
|
||||||
func (vs *VSphere) convertVolPathsToDevicePaths(ctx context.Context, nodeVolumes map[k8stypes.NodeName][]string) (map[k8stypes.NodeName][]string, error) {
|
|
||||||
vmVolumes := make(map[k8stypes.NodeName][]string)
|
|
||||||
for nodeName, volPaths := range nodeVolumes {
|
|
||||||
nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = vs.getVSphereInstanceForServer(nodeInfo.vcServer, ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, volPath := range volPaths {
|
|
||||||
deviceVolPath, err := convertVolPathToDevicePath(ctx, nodeInfo.dataCenter, volPath)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to convert vsphere volume path %s to device path for volume %s. err: %+v", volPath, deviceVolPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
volPaths[i] = deviceVolPath
|
|
||||||
}
|
|
||||||
vmVolumes[nodeName] = volPaths
|
|
||||||
}
|
|
||||||
return vmVolumes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkDiskAttached verifies volumes are attached to the VMs which are in same vCenter and Datacenter
|
|
||||||
// Returns nodes if exist any for which VM is not found in that vCenter and Datacenter
|
|
||||||
func (vs *VSphere) checkDiskAttached(ctx context.Context, nodes []k8stypes.NodeName, nodeVolumes map[k8stypes.NodeName][]string, attached map[string]map[string]bool, retry bool) ([]k8stypes.NodeName, error) {
|
|
||||||
var nodesToRetry []k8stypes.NodeName
|
|
||||||
var vmList []*vclib.VirtualMachine
|
|
||||||
var nodeInfo NodeInfo
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, nodeName := range nodes {
|
|
||||||
nodeInfo, err = vs.nodeManager.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
vmList = append(vmList, nodeInfo.vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making sure session is valid
|
|
||||||
_, err = vs.getVSphereInstanceForServer(nodeInfo.vcServer, ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any of the nodes are not present property collector query will fail for entire operation
|
|
||||||
vmMoList, err := nodeInfo.dataCenter.GetVMMoList(ctx, vmList, []string{"config.hardware.device", "name", "config.uuid"})
|
|
||||||
if err != nil {
|
|
||||||
if vclib.IsManagedObjectNotFoundError(err) && !retry {
|
|
||||||
klog.V(4).Infof("checkDiskAttached: ManagedObjectNotFound for property collector query for nodes: %+v vms: %+v", nodes, vmList)
|
|
||||||
// Property Collector Query failed
|
|
||||||
// VerifyVolumePaths per VM
|
|
||||||
for _, nodeName := range nodes {
|
|
||||||
nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
devices, err := nodeInfo.vm.VirtualMachine.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if vclib.IsManagedObjectNotFoundError(err) {
|
|
||||||
klog.V(4).Infof("checkDiskAttached: ManagedObjectNotFound for Kubernetes node: %s with vSphere Virtual Machine reference: %v", nodeName, nodeInfo.vm)
|
|
||||||
nodesToRetry = append(nodesToRetry, nodeName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Verifying Volume Paths by devices for node %s and VM %s", nodeName, nodeInfo.vm)
|
|
||||||
vs.vsphereVolumeMap.Add(nodeName, devices)
|
|
||||||
vclib.VerifyVolumePathsForVMDevices(devices, nodeVolumes[nodeName], convertToString(nodeName), attached)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vmMoMap := make(map[string]mo.VirtualMachine)
|
|
||||||
for _, vmMo := range vmMoList {
|
|
||||||
if vmMo.Config == nil {
|
|
||||||
klog.Errorf("Config is not available for VM: %q", vmMo.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("vmMoMap vmname: %q vmuuid: %s", vmMo.Name, strings.ToLower(vmMo.Config.Uuid))
|
|
||||||
vmMoMap[strings.ToLower(vmMo.Config.Uuid)] = vmMo
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(9).Infof("vmMoMap: +%v", vmMoMap)
|
|
||||||
|
|
||||||
for _, nodeName := range nodes {
|
|
||||||
node, err := vs.nodeManager.GetNode(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
nodeUUID, err := GetNodeUUID(&node)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Node Discovery failed to get node uuid for node %s with error: %v", node.Name, err)
|
|
||||||
return nodesToRetry, err
|
|
||||||
}
|
|
||||||
nodeUUID = strings.ToLower(nodeUUID)
|
|
||||||
klog.V(9).Infof("Verifying volume for node %s with nodeuuid %q: %v", nodeName, nodeUUID, vmMoMap)
|
|
||||||
vmMo := vmMoMap[nodeUUID]
|
|
||||||
vmDevices := object.VirtualDeviceList(vmMo.Config.Hardware.Device)
|
|
||||||
vs.vsphereVolumeMap.Add(nodeName, vmDevices)
|
|
||||||
vclib.VerifyVolumePathsForVMDevices(vmDevices, nodeVolumes[nodeName], convertToString(nodeName), attached)
|
|
||||||
}
|
|
||||||
return nodesToRetry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildMissingVolumeNodeMap builds a map of volumes and nodes which are not known to attach detach controller.
|
|
||||||
// There could be nodes in cluster which do not have any pods with vsphere volumes running on them
|
|
||||||
// such nodes won't be part of disk verification check because attach-detach controller does not keep track
|
|
||||||
// such nodes. But such nodes may still have dangling volumes on them and hence we need to scan all the
|
|
||||||
// remaining nodes which weren't scanned by code previously.
|
|
||||||
func (vs *VSphere) BuildMissingVolumeNodeMap(ctx context.Context) {
|
|
||||||
nodeNames := vs.nodeManager.GetNodeNames()
|
|
||||||
// Segregate nodes according to VC-DC
|
|
||||||
dcNodes := make(map[string][]k8stypes.NodeName)
|
|
||||||
|
|
||||||
for _, nodeName := range nodeNames {
|
|
||||||
// if given node is not in node volume map
|
|
||||||
if !vs.vsphereVolumeMap.CheckForNode(nodeName) {
|
|
||||||
nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("Failed to get node info: %+v. err: %+v", nodeInfo.vm, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vcDC := nodeInfo.vcServer + nodeInfo.dataCenter.String()
|
|
||||||
dcNodes[vcDC] = append(dcNodes[vcDC], nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for _, nodeNames := range dcNodes {
|
|
||||||
// Start go routines per VC-DC to check disks are attached
|
|
||||||
wg.Add(1)
|
|
||||||
go func(nodes []k8stypes.NodeName) {
|
|
||||||
err := vs.checkNodeDisks(ctx, nodes)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to check disk attached for nodes: %+v. err: %+v", nodes, err)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}(nodeNames)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vs *VSphere) checkNodeDisks(ctx context.Context, nodeNames []k8stypes.NodeName) error {
|
|
||||||
var vmList []*vclib.VirtualMachine
|
|
||||||
var nodeInfo NodeInfo
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for _, nodeName := range nodeNames {
|
|
||||||
nodeInfo, err = vs.nodeManager.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vmList = append(vmList, nodeInfo.vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making sure session is valid
|
|
||||||
_, err = vs.getVSphereInstanceForServer(nodeInfo.vcServer, ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any of the nodes are not present property collector query will fail for entire operation
|
|
||||||
vmMoList, err := nodeInfo.dataCenter.GetVMMoList(ctx, vmList, []string{"config.hardware.device", "name", "config.uuid"})
|
|
||||||
if err != nil {
|
|
||||||
if vclib.IsManagedObjectNotFoundError(err) {
|
|
||||||
klog.V(4).Infof("checkNodeDisks: ManagedObjectNotFound for property collector query for nodes: %+v vms: %+v", nodeNames, vmList)
|
|
||||||
// Property Collector Query failed
|
|
||||||
// VerifyVolumePaths per VM
|
|
||||||
for _, nodeName := range nodeNames {
|
|
||||||
nodeInfo, err := vs.nodeManager.GetNodeInfo(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
devices, err := nodeInfo.vm.VirtualMachine.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if vclib.IsManagedObjectNotFoundError(err) {
|
|
||||||
klog.V(4).Infof("checkNodeDisks: ManagedObjectNotFound for Kubernetes node: %s with vSphere Virtual Machine reference: %v", nodeName, nodeInfo.vm)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Verifying Volume Paths by devices for node %s and VM %s", nodeName, nodeInfo.vm)
|
|
||||||
vs.vsphereVolumeMap.Add(nodeName, devices)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
vmMoMap := make(map[string]mo.VirtualMachine)
|
|
||||||
for _, vmMo := range vmMoList {
|
|
||||||
if vmMo.Config == nil {
|
|
||||||
klog.Errorf("Config is not available for VM: %q", vmMo.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
klog.V(9).Infof("vmMoMap vmname: %q vmuuid: %s", vmMo.Name, strings.ToLower(vmMo.Config.Uuid))
|
|
||||||
vmMoMap[strings.ToLower(vmMo.Config.Uuid)] = vmMo
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(9).Infof("vmMoMap: +%v", vmMoMap)
|
|
||||||
|
|
||||||
for _, nodeName := range nodeNames {
|
|
||||||
node, err := vs.nodeManager.GetNode(nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nodeUUID, err := GetNodeUUID(&node)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Node Discovery failed to get node uuid for node %s with error: %v", node.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nodeUUID = strings.ToLower(nodeUUID)
|
|
||||||
klog.V(9).Infof("Verifying volume for node %s with nodeuuid %q: %v", nodeName, nodeUUID, vmMoMap)
|
|
||||||
vmMo := vmMoMap[nodeUUID]
|
|
||||||
vmDevices := object.VirtualDeviceList(vmMo.Config.Hardware.Device)
|
|
||||||
vs.vsphereVolumeMap.Add(nodeName, vmDevices)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vs *VSphere) GetNodeNameFromProviderID(providerID string) (string, error) {
|
|
||||||
var nodeName string
|
|
||||||
nodes, err := vs.nodeManager.GetNodeDetails()
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error while obtaining Kubernetes node nodeVmDetail details. error : %+v", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, node := range nodes {
|
|
||||||
// ProviderID is UUID for nodes v1.9.3+
|
|
||||||
if node.VMUUID == GetUUIDFromProviderID(providerID) || node.NodeName == providerID {
|
|
||||||
nodeName = node.NodeName
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if nodeName == "" {
|
|
||||||
msg := fmt.Sprintf("Error while obtaining Kubernetes nodename for providerID %s.", providerID)
|
|
||||||
return "", errors.New(msg)
|
|
||||||
}
|
|
||||||
return nodeName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUUIDFromProviderID(providerID string) string {
|
|
||||||
return strings.TrimPrefix(providerID, ProviderPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsUUIDSupportedNode(node *v1.Node) (bool, error) {
|
|
||||||
newVersion, err := version.ParseSemantic("v1.9.4")
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to determine whether node %+v is old with error %v", node, err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
nodeVersion, err := version.ParseSemantic(node.Status.NodeInfo.KubeletVersion)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to determine whether node %+v is old with error %v", node, err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if nodeVersion.LessThan(newVersion) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGuestHardwareVersionDeprecated(vmHardwareversion string) (bool, error) {
|
|
||||||
vmHardwareDeprecated := false
|
|
||||||
// vmconfig.Version returns vm hardware version as vmx-11, vmx-13, vmx-14, vmx-15 etc.
|
|
||||||
version := strings.Trim(vmHardwareversion, "vmx-")
|
|
||||||
value, err := strconv.ParseInt(version, 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to parse vm hardware version: %v Err: %v", version, err)
|
|
||||||
} else {
|
|
||||||
if value < 15 {
|
|
||||||
vmHardwareDeprecated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vmHardwareDeprecated, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetNodeUUID(node *v1.Node) (string, error) {
|
|
||||||
oldNode, err := IsUUIDSupportedNode(node)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to get node UUID for node %+v with error %v", node, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if oldNode {
|
|
||||||
return node.Status.NodeInfo.SystemUUID, nil
|
|
||||||
}
|
|
||||||
return GetUUIDFromProviderID(node.Spec.ProviderID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetVMUUID() (string, error) {
|
|
||||||
uuidFromFile, err := getRawUUID()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error retrieving vm uuid: %s", err)
|
|
||||||
}
|
|
||||||
//strip leading and trailing white space and new line char
|
|
||||||
uuid := strings.TrimSpace(uuidFromFile)
|
|
||||||
// check the uuid starts with "VMware-"
|
|
||||||
if !strings.HasPrefix(uuid, UUIDPrefix) {
|
|
||||||
return "", fmt.Errorf("failed to match Prefix, UUID read from the file is %v", uuidFromFile)
|
|
||||||
}
|
|
||||||
// Strip the prefix and white spaces and -
|
|
||||||
uuid = strings.Replace(uuid[len(UUIDPrefix):(len(uuid))], " ", "", -1)
|
|
||||||
uuid = strings.Replace(uuid, "-", "", -1)
|
|
||||||
if len(uuid) != 32 {
|
|
||||||
return "", fmt.Errorf("length check failed, UUID read from the file is %v", uuidFromFile)
|
|
||||||
}
|
|
||||||
// need to add dashes, e.g. "564d395e-d807-e18a-cb25-b79f65eb2b9f"
|
|
||||||
uuid = fmt.Sprintf("%s-%s-%s-%s-%s", uuid[0:8], uuid[8:12], uuid[12:16], uuid[16:20], uuid[20:32])
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWorkspaceDatacenters returns the Datacenter objects that VCP has access to.
|
|
||||||
// User can configure the list of datacenters in vsphere.conf. Otherwise all the
|
|
||||||
// Datacenters in the configured list of VCs are returned.
|
|
||||||
func (vs *VSphere) GetWorkspaceDatacenters(ctx context.Context) ([]*vclib.Datacenter, error) {
|
|
||||||
var datacenterObjs []*vclib.Datacenter
|
|
||||||
for vc, vsi := range vs.vsphereInstanceMap {
|
|
||||||
// ensure connection to VC
|
|
||||||
err := vs.nodeManager.vcConnect(ctx, vsi)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if vsi.cfg.Datacenters == "" {
|
|
||||||
vcDatacenterObjs, err := vclib.GetAllDatacenter(ctx, vsi.conn)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error fetching list of datacenters from VC %s: %+v", vc, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datacenterObjs = append(datacenterObjs, vcDatacenterObjs...)
|
|
||||||
} else {
|
|
||||||
datacenters := strings.Split(vsi.cfg.Datacenters, ",")
|
|
||||||
for _, dc := range datacenters {
|
|
||||||
dc = strings.TrimSpace(dc)
|
|
||||||
if dc == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datacenterObj, err := vclib.GetDatacenter(ctx, vsi.conn, dc)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error fetching datacenter %s from VC %s: %+v", dc, vc, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
datacenterObjs = append(datacenterObjs, datacenterObj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return datacenterObjs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindDatastoreByName looks for the given datastore by name across all available datacenters.
|
|
||||||
// If more than one Datacenter has a Datastore with the given name, then returns reference to all of them.
|
|
||||||
func (vs *VSphere) FindDatastoreByName(ctx context.Context, datastoreName string) ([]*vclib.DatastoreInfo, error) {
|
|
||||||
datacenters, err := vs.GetWorkspaceDatacenters(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var datastoreInfos []*vclib.DatastoreInfo
|
|
||||||
for _, dc := range datacenters {
|
|
||||||
datastoreInfo, err := dc.GetDatastoreInfoByName(ctx, datastoreName)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(9).Infof("Did not find datastore %s in datacenter %s, still looking.", datastoreName, dc.Name())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datastoreInfos = append(datastoreInfos, datastoreInfo)
|
|
||||||
}
|
|
||||||
if len(datastoreInfos) == 0 {
|
|
||||||
return nil, fmt.Errorf("Datastore '%s' not found", datastoreName)
|
|
||||||
}
|
|
||||||
klog.V(4).Infof("Found datastore infos %v for datastore %s", datastoreInfos, datastoreName)
|
|
||||||
return datastoreInfos, nil
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
//go:build !providerless && linux
|
|
||||||
// +build !providerless,linux
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
const UUIDPath = "/sys/class/dmi/id/product_serial"
|
|
||||||
|
|
||||||
func getRawUUID() (string, error) {
|
|
||||||
id, err := ioutil.ReadFile(UUIDPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(id), nil
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2019 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/legacy-cloud-providers/vsphere/vclib"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/simulator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetPathFromFileNotFound(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// vCenter model + initial set of objects (cluster, hosts, VMs, network, datastore, etc)
|
|
||||||
model := simulator.VPX()
|
|
||||||
|
|
||||||
defer model.Remove()
|
|
||||||
err := model.Create()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := model.Service.NewServer()
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
c, err := govmomi.NewClient(ctx, s.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vc := &vclib.VSphereConnection{Client: c.Client}
|
|
||||||
|
|
||||||
dc, err := vclib.GetDatacenter(ctx, vc, vclib.TestDefaultDatacenter)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to get datacenter: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDiskPath := fmt.Sprintf("[%s] %s", vclib.TestDefaultDatastore, DummyDiskName)
|
|
||||||
_, err = dc.GetVirtualDiskPage83Data(ctx, requestDiskPath)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error when calling GetVirtualDiskPage83Data")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = getPathFromFileNotFound(err)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected err to be nil but was %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = getPathFromFileNotFound(nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected err when calling getPathFromFileNotFound with nil err")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMX15Deprecated(t *testing.T) {
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated("vmx-15")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if vmhardwaredeprecated {
|
|
||||||
t.Fatal("vmx-15 should not be deprecated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMX14Deprecated(t *testing.T) {
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated("vmx-14")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !vmhardwaredeprecated {
|
|
||||||
t.Fatal("vmx-14 should be deprecated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMX13Deprecated(t *testing.T) {
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated("vmx-13")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !vmhardwaredeprecated {
|
|
||||||
t.Fatal("vmx-13 should be deprecated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMX11Deprecated(t *testing.T) {
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated("vmx-11")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !vmhardwaredeprecated {
|
|
||||||
t.Fatal("vmx-11 should be deprecated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMX17Deprecated(t *testing.T) {
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated("vmx-17")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if vmhardwaredeprecated {
|
|
||||||
t.Fatal("vmx-17 should not be deprecated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVMX18Deprecated(t *testing.T) {
|
|
||||||
vmhardwaredeprecated, err := isGuestHardwareVersionDeprecated("vmx-18")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if vmhardwaredeprecated {
|
|
||||||
t.Fatal("vmx-18 should not be deprecated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
//go:build !providerless && !windows && !linux
|
|
||||||
// +build !providerless,!windows,!linux
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func getRawUUID() (string, error) {
|
|
||||||
return "", fmt.Errorf("Retrieving VM UUID on this build is not implemented.")
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
//go:build !providerless && windows
|
|
||||||
// +build !providerless,windows
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getRawUUID() (string, error) {
|
|
||||||
result, err := exec.Command("wmic", "bios", "get", "serialnumber").Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
lines := strings.FieldsFunc(string(result), func(r rune) bool {
|
|
||||||
switch r {
|
|
||||||
case '\n', '\r':
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if len(lines) != 2 {
|
|
||||||
return "", fmt.Errorf("received unexpected value retrieving vm uuid: %q", string(result))
|
|
||||||
}
|
|
||||||
return lines[1], nil
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2020 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type volumePath string
|
|
||||||
|
|
||||||
type nodeVolumeStatus struct {
|
|
||||||
nodeName k8stypes.NodeName
|
|
||||||
verified bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// VsphereVolumeMap stores last known state of node and volume mapping
|
|
||||||
type VsphereVolumeMap struct {
|
|
||||||
volumeNodeMap map[volumePath]nodeVolumeStatus
|
|
||||||
nodeMap map[k8stypes.NodeName]bool
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVsphereVolumeMap() *VsphereVolumeMap {
|
|
||||||
return &VsphereVolumeMap{
|
|
||||||
volumeNodeMap: map[volumePath]nodeVolumeStatus{},
|
|
||||||
nodeMap: map[k8stypes.NodeName]bool{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartDiskVerification marks all known volumes as unverified so as
|
|
||||||
// disks which aren't verified can be removed at the end of verification process
|
|
||||||
func (vsphereVolume *VsphereVolumeMap) StartDiskVerification() {
|
|
||||||
vsphereVolume.lock.Lock()
|
|
||||||
defer vsphereVolume.lock.Unlock()
|
|
||||||
for k, v := range vsphereVolume.volumeNodeMap {
|
|
||||||
v.verified = false
|
|
||||||
vsphereVolume.volumeNodeMap[k] = v
|
|
||||||
}
|
|
||||||
// reset nodeMap to empty so that any node we could not verify via usual verification process
|
|
||||||
// can still be verified.
|
|
||||||
vsphereVolume.nodeMap = map[k8stypes.NodeName]bool{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckForVolume verifies if disk is attached to some node in the cluster.
|
|
||||||
// This check is not definitive and should be followed up by separate verification.
|
|
||||||
func (vsphereVolume *VsphereVolumeMap) CheckForVolume(path string) (k8stypes.NodeName, bool) {
|
|
||||||
vsphereVolume.lock.RLock()
|
|
||||||
defer vsphereVolume.lock.RUnlock()
|
|
||||||
vPath := volumePath(path)
|
|
||||||
ns, ok := vsphereVolume.volumeNodeMap[vPath]
|
|
||||||
if ok {
|
|
||||||
return ns.nodeName, true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckForNode returns true if given node has already been processed by volume
|
|
||||||
// verification mechanism. This is used to skip verifying attached disks on nodes
|
|
||||||
// which were previously verified.
|
|
||||||
func (vsphereVolume *VsphereVolumeMap) CheckForNode(nodeName k8stypes.NodeName) bool {
|
|
||||||
vsphereVolume.lock.RLock()
|
|
||||||
defer vsphereVolume.lock.RUnlock()
|
|
||||||
_, ok := vsphereVolume.nodeMap[nodeName]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all devices found on a node to the device map
|
|
||||||
func (vsphereVolume *VsphereVolumeMap) Add(node k8stypes.NodeName, vmDevices object.VirtualDeviceList) {
|
|
||||||
vsphereVolume.lock.Lock()
|
|
||||||
defer vsphereVolume.lock.Unlock()
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
if vmDevices.TypeName(device) == "VirtualDisk" {
|
|
||||||
virtualDevice := device.GetVirtualDevice()
|
|
||||||
if backing, ok := virtualDevice.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
|
|
||||||
filename := volumePath(backing.FileName)
|
|
||||||
vsphereVolume.volumeNodeMap[filename] = nodeVolumeStatus{node, true}
|
|
||||||
vsphereVolume.nodeMap[node] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveUnverified will remove any device which we could not verify to be attached to a node.
|
|
||||||
func (vsphereVolume *VsphereVolumeMap) RemoveUnverified() {
|
|
||||||
vsphereVolume.lock.Lock()
|
|
||||||
defer vsphereVolume.lock.Unlock()
|
|
||||||
for k, v := range vsphereVolume.volumeNodeMap {
|
|
||||||
if !v.verified {
|
|
||||||
delete(vsphereVolume.volumeNodeMap, k)
|
|
||||||
delete(vsphereVolume.nodeMap, v.nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
//go:build !providerless
|
|
||||||
// +build !providerless
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright 2020 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVsphereVolumeMap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
deviceToAdd object.VirtualDeviceList
|
|
||||||
nodeToAdd k8stypes.NodeName
|
|
||||||
checkRunner func(volumeMap *VsphereVolumeMap)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "adding new volume",
|
|
||||||
deviceToAdd: getVirtualDeviceList("[foobar] kubevols/foo.vmdk"),
|
|
||||||
nodeToAdd: convertToK8sType("node1.lan"),
|
|
||||||
checkRunner: func(volumeMap *VsphereVolumeMap) {
|
|
||||||
volumeToCheck := "[foobar] kubevols/foo.vmdk"
|
|
||||||
_, ok := volumeMap.CheckForVolume(volumeToCheck)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("error checking volume %s, expected true got %v", volumeToCheck, ok)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "mismatching volume",
|
|
||||||
deviceToAdd: getVirtualDeviceList("[foobar] kubevols/foo.vmdk"),
|
|
||||||
nodeToAdd: convertToK8sType("node1.lan"),
|
|
||||||
checkRunner: func(volumeMap *VsphereVolumeMap) {
|
|
||||||
volumeToCheck := "[foobar] kubevols/bar.vmdk"
|
|
||||||
_, ok := volumeMap.CheckForVolume(volumeToCheck)
|
|
||||||
if ok {
|
|
||||||
t.Errorf("error checking volume %s, expected false got %v", volumeToCheck, ok)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "should remove unverified devices",
|
|
||||||
deviceToAdd: getVirtualDeviceList("[foobar] kubevols/foo.vmdk"),
|
|
||||||
nodeToAdd: convertToK8sType("node1.lan"),
|
|
||||||
checkRunner: func(volumeMap *VsphereVolumeMap) {
|
|
||||||
volumeMap.StartDiskVerification()
|
|
||||||
volumeMap.RemoveUnverified()
|
|
||||||
volumeToCheck := "[foobar] kubevols/foo.vmdk"
|
|
||||||
_, ok := volumeMap.CheckForVolume(volumeToCheck)
|
|
||||||
if ok {
|
|
||||||
t.Errorf("error checking volume %s, expected false got %v", volumeToCheck, ok)
|
|
||||||
}
|
|
||||||
node := k8stypes.NodeName("node1.lan")
|
|
||||||
ok = volumeMap.CheckForNode(node)
|
|
||||||
if ok {
|
|
||||||
t.Errorf("unexpected node %s in node map", node)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "node check should return false for previously added node",
|
|
||||||
deviceToAdd: getVirtualDeviceList("[foobar] kubevols/foo.vmdk"),
|
|
||||||
nodeToAdd: convertToK8sType("node1.lan"),
|
|
||||||
checkRunner: func(volumeMap *VsphereVolumeMap) {
|
|
||||||
volumeMap.StartDiskVerification()
|
|
||||||
node := k8stypes.NodeName("node1.lan")
|
|
||||||
ok := volumeMap.CheckForNode(node)
|
|
||||||
if ok {
|
|
||||||
t.Errorf("unexpected node %s in node map", node)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
vMap := NewVsphereVolumeMap()
|
|
||||||
vMap.Add(tc.nodeToAdd, tc.deviceToAdd)
|
|
||||||
tc.checkRunner(vMap)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVirtualDeviceList(vPath string) object.VirtualDeviceList {
|
|
||||||
return object.VirtualDeviceList{
|
|
||||||
&types.VirtualDisk{
|
|
||||||
VirtualDevice: types.VirtualDevice{
|
|
||||||
Key: 1000,
|
|
||||||
Backing: &types.VirtualDiskFlatVer2BackingInfo{
|
|
||||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
|
||||||
FileName: vPath,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -61,7 +61,6 @@ import (
|
|||||||
e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
|
e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
|
||||||
storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
|
storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
"k8s.io/kubernetes/test/e2e/storage/utils"
|
||||||
vspheretest "k8s.io/kubernetes/test/e2e/storage/vsphere"
|
|
||||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1185,7 +1184,6 @@ type vSphereDriver struct {
|
|||||||
|
|
||||||
type vSphereVolume struct {
|
type vSphereVolume struct {
|
||||||
volumePath string
|
volumePath string
|
||||||
nodeInfo *vspheretest.NodeInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ storageframework.TestDriver = &vSphereDriver{}
|
var _ storageframework.TestDriver = &vSphereDriver{}
|
||||||
@@ -1286,17 +1284,6 @@ func (v *vSphereDriver) GetDynamicProvisionStorageClass(ctx context.Context, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *vSphereDriver) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig {
|
func (v *vSphereDriver) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig {
|
||||||
vspheretest.Bootstrap(f)
|
|
||||||
ginkgo.DeferCleanup(func(ctx context.Context) {
|
|
||||||
// Driver Cleanup function
|
|
||||||
// Logout each vSphere client connection to prevent session leakage
|
|
||||||
nodes := vspheretest.GetReadySchedulableNodeInfos(ctx, f.ClientSet)
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node.VSphere.Client != nil {
|
|
||||||
_ = node.VSphere.Client.Logout(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return &storageframework.PerTestConfig{
|
return &storageframework.PerTestConfig{
|
||||||
Driver: v,
|
Driver: v,
|
||||||
Prefix: "vsphere",
|
Prefix: "vsphere",
|
||||||
@@ -1305,18 +1292,10 @@ func (v *vSphereDriver) PrepareTest(ctx context.Context, f *framework.Framework)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *vSphereDriver) CreateVolume(ctx context.Context, config *storageframework.PerTestConfig, volType storageframework.TestVolType) storageframework.TestVolume {
|
func (v *vSphereDriver) CreateVolume(ctx context.Context, config *storageframework.PerTestConfig, volType storageframework.TestVolType) storageframework.TestVolume {
|
||||||
f := config.Framework
|
return &vSphereVolume{}
|
||||||
nodeInfo := vspheretest.GetReadySchedulableRandomNodeInfo(ctx, f.ClientSet)
|
|
||||||
volumePath, err := nodeInfo.VSphere.CreateVolume(&vspheretest.VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
return &vSphereVolume{
|
|
||||||
volumePath: volumePath,
|
|
||||||
nodeInfo: nodeInfo,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *vSphereVolume) DeleteVolume(ctx context.Context) {
|
func (v *vSphereVolume) DeleteVolume(ctx context.Context) {
|
||||||
v.nodeInfo.VSphere.DeleteVolume(v.volumePath, v.nodeInfo.DataCenterRef)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Azure Disk
|
// Azure Disk
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
)
|
|
||||||
|
|
||||||
var once sync.Once
|
|
||||||
var waiting = make(chan bool)
|
|
||||||
|
|
||||||
// Bootstrap takes care of initializing necessary test context for vSphere tests
|
|
||||||
func Bootstrap(fw *framework.Framework) {
|
|
||||||
done := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
once.Do(func() {
|
|
||||||
bootstrapOnce(fw)
|
|
||||||
})
|
|
||||||
<-waiting
|
|
||||||
done <- true
|
|
||||||
}()
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
|
|
||||||
func bootstrapOnce(f *framework.Framework) {
|
|
||||||
// 1. Read vSphere conf and get VSphere instances
|
|
||||||
vsphereInstances, err := GetVSphereInstances()
|
|
||||||
if err != nil {
|
|
||||||
framework.Failf("Failed to bootstrap vSphere with error: %v", err)
|
|
||||||
}
|
|
||||||
// 2. Get all nodes
|
|
||||||
nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
framework.Failf("Failed to get nodes: %v", err)
|
|
||||||
}
|
|
||||||
TestContext = Context{NodeMapper: NewNodeMapper(), VSphereInstances: vsphereInstances}
|
|
||||||
// 3. Get Node to VSphere mapping
|
|
||||||
err = TestContext.NodeMapper.GenerateNodeMap(vsphereInstances, *nodeList)
|
|
||||||
if err != nil {
|
|
||||||
framework.Failf("Failed to bootstrap vSphere with error: %v", err)
|
|
||||||
}
|
|
||||||
// 4. Generate Zone to Datastore mapping
|
|
||||||
err = TestContext.NodeMapper.GenerateZoneToDatastoreMap()
|
|
||||||
if err != nil {
|
|
||||||
framework.Failf("Failed to generate zone to datastore mapping with error: %v", err)
|
|
||||||
}
|
|
||||||
close(waiting)
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gopkg.in/gcfg.v1"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
vSphereConfFileEnvVar = "VSPHERE_CONF_FILE"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
confFileLocation = os.Getenv(vSphereConfFileEnvVar)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents vSphere configuration
|
|
||||||
type Config struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Hostname string
|
|
||||||
Port string
|
|
||||||
Datacenters string
|
|
||||||
RoundTripperCount uint
|
|
||||||
DefaultDatastore string
|
|
||||||
Folder string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigFile represents the content of vsphere.conf file.
|
|
||||||
// Users specify the configuration of one or more vSphere instances in vsphere.conf where
|
|
||||||
// the Kubernetes master and worker nodes are running.
|
|
||||||
type ConfigFile struct {
|
|
||||||
Global struct {
|
|
||||||
// vCenter username.
|
|
||||||
User string `gcfg:"user"`
|
|
||||||
// vCenter password in clear text.
|
|
||||||
Password string `gcfg:"password"`
|
|
||||||
// vCenter port.
|
|
||||||
VCenterPort string `gcfg:"port"`
|
|
||||||
// True if vCenter uses self-signed cert.
|
|
||||||
InsecureFlag bool `gcfg:"insecure-flag"`
|
|
||||||
// Datacenter in which VMs are located.
|
|
||||||
Datacenters string `gcfg:"datacenters"`
|
|
||||||
// Soap round tripper count (retries = RoundTripper - 1)
|
|
||||||
RoundTripperCount uint `gcfg:"soap-roundtrip-count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualCenter map[string]*Config
|
|
||||||
|
|
||||||
Network struct {
|
|
||||||
// PublicNetwork is name of the network the VMs are joined to.
|
|
||||||
PublicNetwork string `gcfg:"public-network"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Disk struct {
|
|
||||||
// SCSIControllerType defines SCSI controller to be used.
|
|
||||||
SCSIControllerType string `dcfg:"scsicontrollertype"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoint used to create volumes
|
|
||||||
Workspace struct {
|
|
||||||
VCenterIP string `gcfg:"server"`
|
|
||||||
Datacenter string `gcfg:"datacenter"`
|
|
||||||
Folder string `gcfg:"folder"`
|
|
||||||
DefaultDatastore string `gcfg:"default-datastore"`
|
|
||||||
ResourcePoolPath string `gcfg:"resourcepool-path"`
|
|
||||||
}
|
|
||||||
// Tag categories and tags which correspond to "built-in node labels: zones and region"
|
|
||||||
Labels struct {
|
|
||||||
Zone string `gcfg:"zone"`
|
|
||||||
Region string `gcfg:"region"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVSphereInstances parses vsphere.conf and returns VSphere instances
|
|
||||||
func GetVSphereInstances() (map[string]*VSphere, error) {
|
|
||||||
cfg, err := getConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return populateInstanceMap(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfig() (*ConfigFile, error) {
|
|
||||||
if confFileLocation == "" {
|
|
||||||
if framework.TestContext.CloudConfig.ConfigFile == "" {
|
|
||||||
return nil, fmt.Errorf("env variable 'VSPHERE_CONF_FILE' is not set, and no config-file specified")
|
|
||||||
}
|
|
||||||
confFileLocation = framework.TestContext.CloudConfig.ConfigFile
|
|
||||||
}
|
|
||||||
confFile, err := os.Open(confFileLocation)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer confFile.Close()
|
|
||||||
cfg, err := readConfig(confFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readConfig parses vSphere cloud config file into ConfigFile.
|
|
||||||
func readConfig(config io.Reader) (ConfigFile, error) {
|
|
||||||
if config == nil {
|
|
||||||
err := fmt.Errorf("no vSphere cloud provider config file given")
|
|
||||||
return ConfigFile{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg ConfigFile
|
|
||||||
err := gcfg.ReadInto(&cfg, config)
|
|
||||||
return cfg, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateInstanceMap(cfg *ConfigFile) (map[string]*VSphere, error) {
|
|
||||||
vsphereInstances := make(map[string]*VSphere)
|
|
||||||
|
|
||||||
if cfg.Workspace.VCenterIP == "" || cfg.Workspace.DefaultDatastore == "" || cfg.Workspace.Folder == "" || cfg.Workspace.Datacenter == "" {
|
|
||||||
msg := fmt.Sprintf("All fields in workspace are mandatory."+
|
|
||||||
" vsphere.conf does not have the workspace specified correctly. cfg.Workspace: %+v", cfg.Workspace)
|
|
||||||
framework.Logf(msg)
|
|
||||||
return nil, errors.New(msg)
|
|
||||||
}
|
|
||||||
for vcServer, vcConfig := range cfg.VirtualCenter {
|
|
||||||
framework.Logf("Initializing vc server %s", vcServer)
|
|
||||||
if vcServer == "" {
|
|
||||||
framework.Logf("vsphere.conf does not have the VirtualCenter IP address specified")
|
|
||||||
return nil, errors.New("vsphere.conf does not have the VirtualCenter IP address specified")
|
|
||||||
}
|
|
||||||
vcConfig.Hostname = vcServer
|
|
||||||
|
|
||||||
if vcConfig.Username == "" {
|
|
||||||
vcConfig.Username = cfg.Global.User
|
|
||||||
}
|
|
||||||
if vcConfig.Password == "" {
|
|
||||||
vcConfig.Password = cfg.Global.Password
|
|
||||||
}
|
|
||||||
if vcConfig.Username == "" {
|
|
||||||
msg := fmt.Sprintf("vcConfig.Username is empty for vc %s!", vcServer)
|
|
||||||
framework.Logf(msg)
|
|
||||||
return nil, errors.New(msg)
|
|
||||||
}
|
|
||||||
if vcConfig.Password == "" {
|
|
||||||
msg := fmt.Sprintf("vcConfig.Password is empty for vc %s!", vcServer)
|
|
||||||
framework.Logf(msg)
|
|
||||||
return nil, errors.New(msg)
|
|
||||||
}
|
|
||||||
if vcConfig.Port == "" {
|
|
||||||
vcConfig.Port = cfg.Global.VCenterPort
|
|
||||||
}
|
|
||||||
if vcConfig.Datacenters == "" && cfg.Global.Datacenters != "" {
|
|
||||||
vcConfig.Datacenters = cfg.Global.Datacenters
|
|
||||||
}
|
|
||||||
if vcConfig.RoundTripperCount == 0 {
|
|
||||||
vcConfig.RoundTripperCount = cfg.Global.RoundTripperCount
|
|
||||||
}
|
|
||||||
|
|
||||||
vcConfig.DefaultDatastore = cfg.Workspace.DefaultDatastore
|
|
||||||
vcConfig.Folder = cfg.Workspace.Folder
|
|
||||||
|
|
||||||
vsphereIns := VSphere{
|
|
||||||
Config: vcConfig,
|
|
||||||
}
|
|
||||||
vsphereInstances[vcServer] = &vsphereIns
|
|
||||||
}
|
|
||||||
|
|
||||||
framework.Logf("vSphere instances: %v", vsphereInstances)
|
|
||||||
return vsphereInstances, nil
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
neturl "net/url"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/session"
|
|
||||||
"github.com/vmware/govmomi/vim25"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
roundTripperDefaultCount = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientLock sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// Connect makes connection to vSphere
|
|
||||||
// No actions are taken if a connection exists and alive. Otherwise, a new client will be created.
|
|
||||||
func Connect(ctx context.Context, vs *VSphere) error {
|
|
||||||
var err error
|
|
||||||
clientLock.Lock()
|
|
||||||
defer clientLock.Unlock()
|
|
||||||
|
|
||||||
if vs.Client == nil {
|
|
||||||
vs.Client, err = NewClient(ctx, vs)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create govmomi client. err: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
manager := session.NewManager(vs.Client.Client)
|
|
||||||
userSession, err := manager.UserSession(ctx)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error while obtaining user session. err: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if userSession != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
klog.Warningf("Creating new client session since the existing session is not valid or not authenticated")
|
|
||||||
vs.Client.Logout(ctx)
|
|
||||||
vs.Client, err = NewClient(ctx, vs)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create govmomi client. err: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new client for vSphere connection
|
|
||||||
func NewClient(ctx context.Context, vs *VSphere) (*govmomi.Client, error) {
|
|
||||||
url, err := neturl.Parse(fmt.Sprintf("https://%s/sdk", net.JoinHostPort(vs.Config.Hostname, vs.Config.Port)))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to parse URL: %s. err: %+v", url, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
url.User = neturl.UserPassword(vs.Config.Username, vs.Config.Password)
|
|
||||||
client, err := govmomi.NewClient(ctx, url, true)
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Failed to create new client. err: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if vs.Config.RoundTripperCount == 0 {
|
|
||||||
vs.Config.RoundTripperCount = roundTripperDefaultCount
|
|
||||||
}
|
|
||||||
client.RoundTripper = vim25.Retry(client.RoundTripper, vim25.TemporaryNetworkError(int(vs.Config.RoundTripperCount)))
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
// Context holds common information for vSphere tests
|
|
||||||
type Context struct {
|
|
||||||
NodeMapper *NodeMapper
|
|
||||||
VSphereInstances map[string]*VSphere
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestContext should be used by all tests to access common context data. It should be initialized only once, during bootstrapping the tests.
|
|
||||||
var TestContext Context
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vapi/rest"
|
|
||||||
"github.com/vmware/govmomi/vapi/tags"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
|
|
||||||
neturl "net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeMapper contains information to generate nameToNodeInfo and vcToZoneDatastore maps
|
|
||||||
type NodeMapper struct {
|
|
||||||
nodeInfoRWLock *sync.RWMutex
|
|
||||||
nameToNodeInfo map[string]*NodeInfo
|
|
||||||
vcToZoneDatastoresMap map[string](map[string][]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeInfo contains information about vcenter nodes
|
|
||||||
type NodeInfo struct {
|
|
||||||
Name string
|
|
||||||
DataCenterRef types.ManagedObjectReference
|
|
||||||
VirtualMachineRef types.ManagedObjectReference
|
|
||||||
HostSystemRef types.ManagedObjectReference
|
|
||||||
VSphere *VSphere
|
|
||||||
Zones []string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
datacenterType = "Datacenter"
|
|
||||||
clusterComputeResourceType = "ClusterComputeResource"
|
|
||||||
hostSystemType = "HostSystem"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewNodeMapper returns a new NodeMapper
|
|
||||||
func NewNodeMapper() *NodeMapper {
|
|
||||||
return &NodeMapper{
|
|
||||||
nodeInfoRWLock: &sync.RWMutex{},
|
|
||||||
nameToNodeInfo: make(map[string]*NodeInfo),
|
|
||||||
vcToZoneDatastoresMap: make(map[string](map[string][]string)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateNodeMap populates node name to node info map
|
|
||||||
func (nm *NodeMapper) GenerateNodeMap(vSphereInstances map[string]*VSphere, nodeList v1.NodeList) error {
|
|
||||||
type VMSearch struct {
|
|
||||||
vs *VSphere
|
|
||||||
datacenter *object.Datacenter
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var queueChannel []*VMSearch
|
|
||||||
|
|
||||||
var datacenters []*object.Datacenter
|
|
||||||
var err error
|
|
||||||
for _, vs := range vSphereInstances {
|
|
||||||
|
|
||||||
// Create context
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if vs.Config.Datacenters == "" {
|
|
||||||
datacenters, err = vs.GetAllDatacenter(ctx)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("NodeMapper error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dcName := strings.Split(vs.Config.Datacenters, ",")
|
|
||||||
for _, dc := range dcName {
|
|
||||||
dc = strings.TrimSpace(dc)
|
|
||||||
if dc == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datacenter, err := vs.GetDatacenter(ctx, dc)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("NodeMapper error dc: %s \n err: %v", dc, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datacenters = append(datacenters, datacenter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dc := range datacenters {
|
|
||||||
framework.Logf("Search candidates vc=%s and datacenter=%s", vs.Config.Hostname, dc.Name())
|
|
||||||
queueChannel = append(queueChannel, &VMSearch{vs: vs, datacenter: dc})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range nodeList.Items {
|
|
||||||
n := node
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
nodeUUID := getUUIDFromProviderID(n.Spec.ProviderID)
|
|
||||||
framework.Logf("Searching for node with UUID: %s", nodeUUID)
|
|
||||||
for _, res := range queueChannel {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
vm, err := res.vs.GetVMByUUID(ctx, nodeUUID, res.datacenter)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Error %v while looking for node=%s in vc=%s and datacenter=%s",
|
|
||||||
err, n.Name, res.vs.Config.Hostname, res.datacenter.Name())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if vm != nil {
|
|
||||||
hostSystemRef := res.vs.GetHostFromVMReference(ctx, vm.Reference())
|
|
||||||
zones := retrieveZoneInformationForNode(n.Name, res.vs, hostSystemRef)
|
|
||||||
framework.Logf("Found node %s as vm=%+v placed on host=%+v under zones %s in vc=%s and datacenter=%s",
|
|
||||||
n.Name, vm, hostSystemRef, zones, res.vs.Config.Hostname, res.datacenter.Name())
|
|
||||||
nodeInfo := &NodeInfo{Name: n.Name, DataCenterRef: res.datacenter.Reference(), VirtualMachineRef: vm.Reference(), HostSystemRef: hostSystemRef, VSphere: res.vs, Zones: zones}
|
|
||||||
nm.SetNodeInfo(n.Name, nodeInfo)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if len(nm.nameToNodeInfo) != len(nodeList.Items) {
|
|
||||||
return errors.New("all nodes not mapped to respective vSphere")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish rest connection to retrieve tag manager stub
|
|
||||||
func withTagsClient(ctx context.Context, connection *VSphere, f func(c *rest.Client) error) error {
|
|
||||||
c := rest.NewClient(connection.Client.Client)
|
|
||||||
user := neturl.UserPassword(connection.Config.Username, connection.Config.Password)
|
|
||||||
if err := c.Login(ctx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ginkgo.DeferCleanup(c.Logout)
|
|
||||||
return f(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterates over each node and retrieves the zones in which they are placed
|
|
||||||
func retrieveZoneInformationForNode(nodeName string, connection *VSphere, hostSystemRef types.ManagedObjectReference) []string {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
var zones []string
|
|
||||||
pc := connection.Client.ServiceContent.PropertyCollector
|
|
||||||
withTagsClient(ctx, connection, func(c *rest.Client) error {
|
|
||||||
client := tags.NewManager(c)
|
|
||||||
// Example result: ["Host", "Cluster", "Datacenter"]
|
|
||||||
ancestors, err := mo.Ancestors(ctx, connection.Client, pc, hostSystemRef)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var validAncestors []mo.ManagedEntity
|
|
||||||
// Filter out only Datacenter, ClusterComputeResource and HostSystem type objects. These objects will be
|
|
||||||
// in the following order ["Datacenter" < "ClusterComputeResource" < "HostSystem"] so that the highest
|
|
||||||
// zone precedence will be received by the HostSystem type.
|
|
||||||
for _, ancestor := range ancestors {
|
|
||||||
moType := ancestor.ExtensibleManagedObject.Self.Type
|
|
||||||
if moType == datacenterType || moType == clusterComputeResourceType || moType == hostSystemType {
|
|
||||||
validAncestors = append(validAncestors, ancestor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ancestor := range validAncestors {
|
|
||||||
var zonesAttachedToObject []string
|
|
||||||
tags, err := client.ListAttachedTags(ctx, ancestor)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, value := range tags {
|
|
||||||
tag, err := client.GetTag(ctx, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
category, err := client.GetCategory(ctx, tag.CategoryID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case category.Name == "k8s-zone":
|
|
||||||
framework.Logf("Found %s associated with %s for %s", tag.Name, ancestor.Name, nodeName)
|
|
||||||
zonesAttachedToObject = append(zonesAttachedToObject, tag.Name)
|
|
||||||
case category.Name == "k8s-region":
|
|
||||||
framework.Logf("Found %s associated with %s for %s", tag.Name, ancestor.Name, nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Overwrite zone information if it exists for this object
|
|
||||||
if len(zonesAttachedToObject) != 0 {
|
|
||||||
zones = zonesAttachedToObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return zones
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateZoneToDatastoreMap generates a mapping of zone to datastore for easily verifying volume placement
|
|
||||||
func (nm *NodeMapper) GenerateZoneToDatastoreMap() error {
|
|
||||||
// 1. Create zone to hosts map for each VC
|
|
||||||
var vcToZoneHostsMap = make(map[string](map[string][]string))
|
|
||||||
// 2. Create host to datastores map for each VC
|
|
||||||
var vcToHostDatastoresMap = make(map[string](map[string][]string))
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
// 3. Populate vcToZoneHostsMap and vcToHostDatastoresMap
|
|
||||||
for _, nodeInfo := range nm.nameToNodeInfo {
|
|
||||||
vc := nodeInfo.VSphere.Config.Hostname
|
|
||||||
host := nodeInfo.HostSystemRef.Value
|
|
||||||
for _, zone := range nodeInfo.Zones {
|
|
||||||
if vcToZoneHostsMap[vc] == nil {
|
|
||||||
vcToZoneHostsMap[vc] = make(map[string][]string)
|
|
||||||
}
|
|
||||||
// Populating vcToZoneHostsMap using the HostSystemRef and Zone fields from each NodeInfo
|
|
||||||
hosts := vcToZoneHostsMap[vc][zone]
|
|
||||||
hosts = append(hosts, host)
|
|
||||||
vcToZoneHostsMap[vc][zone] = hosts
|
|
||||||
}
|
|
||||||
if vcToHostDatastoresMap[vc] == nil {
|
|
||||||
vcToHostDatastoresMap[vc] = make(map[string][]string)
|
|
||||||
}
|
|
||||||
datastores := vcToHostDatastoresMap[vc][host]
|
|
||||||
// Populating vcToHostDatastoresMap by finding out the datastores mounted on node's host
|
|
||||||
datastoreRefs := nodeInfo.VSphere.GetDatastoresMountedOnHost(ctx, nodeInfo.HostSystemRef)
|
|
||||||
for _, datastore := range datastoreRefs {
|
|
||||||
datastores = append(datastores, datastore.Value)
|
|
||||||
}
|
|
||||||
vcToHostDatastoresMap[vc][host] = datastores
|
|
||||||
}
|
|
||||||
// 4, Populate vcToZoneDatastoresMap from vcToZoneHostsMap and vcToHostDatastoresMap
|
|
||||||
for vc, zoneToHostsMap := range vcToZoneHostsMap {
|
|
||||||
for zone, hosts := range zoneToHostsMap {
|
|
||||||
commonDatastores := retrieveCommonDatastoresAmongHosts(hosts, vcToHostDatastoresMap[vc])
|
|
||||||
if nm.vcToZoneDatastoresMap[vc] == nil {
|
|
||||||
nm.vcToZoneDatastoresMap[vc] = make(map[string][]string)
|
|
||||||
}
|
|
||||||
nm.vcToZoneDatastoresMap[vc][zone] = commonDatastores
|
|
||||||
}
|
|
||||||
}
|
|
||||||
framework.Logf("Zone to datastores map : %+v", nm.vcToZoneDatastoresMap)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieveCommonDatastoresAmongHosts retrieves the common datastores from the specified hosts
|
|
||||||
func retrieveCommonDatastoresAmongHosts(hosts []string, hostToDatastoresMap map[string][]string) []string {
|
|
||||||
var datastoreCountMap = make(map[string]int)
|
|
||||||
for _, host := range hosts {
|
|
||||||
for _, datastore := range hostToDatastoresMap[host] {
|
|
||||||
datastoreCountMap[datastore] = datastoreCountMap[datastore] + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var commonDatastores []string
|
|
||||||
numHosts := len(hosts)
|
|
||||||
for datastore, count := range datastoreCountMap {
|
|
||||||
if count == numHosts {
|
|
||||||
commonDatastores = append(commonDatastores, datastore)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commonDatastores
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoresInZone returns all the datastores in the specified zone
|
|
||||||
func (nm *NodeMapper) GetDatastoresInZone(vc string, zone string) []string {
|
|
||||||
nm.nodeInfoRWLock.RLock()
|
|
||||||
defer nm.nodeInfoRWLock.RUnlock()
|
|
||||||
return nm.vcToZoneDatastoresMap[vc][zone]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeInfo returns NodeInfo for given nodeName
|
|
||||||
func (nm *NodeMapper) GetNodeInfo(nodeName string) *NodeInfo {
|
|
||||||
nm.nodeInfoRWLock.RLock()
|
|
||||||
defer nm.nodeInfoRWLock.RUnlock()
|
|
||||||
return nm.nameToNodeInfo[nodeName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeInfo sets NodeInfo for given nodeName. This function is not thread safe. Users need to handle concurrency.
|
|
||||||
func (nm *NodeMapper) SetNodeInfo(nodeName string, nodeInfo *NodeInfo) {
|
|
||||||
nm.nodeInfoRWLock.Lock()
|
|
||||||
defer nm.nodeInfoRWLock.Unlock()
|
|
||||||
nm.nameToNodeInfo[nodeName] = nodeInfo
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Testing configurations of single a PV/PVC pair attached to a vSphere Disk
|
|
||||||
var _ = utils.SIGDescribe("PersistentVolumes:vsphere", feature.Vsphere, func() {
|
|
||||||
var (
|
|
||||||
c clientset.Interface
|
|
||||||
ns string
|
|
||||||
volumePath string
|
|
||||||
pv *v1.PersistentVolume
|
|
||||||
pvc *v1.PersistentVolumeClaim
|
|
||||||
clientPod *v1.Pod
|
|
||||||
pvConfig e2epv.PersistentVolumeConfig
|
|
||||||
pvcConfig e2epv.PersistentVolumeClaimConfig
|
|
||||||
err error
|
|
||||||
node string
|
|
||||||
volLabel labels.Set
|
|
||||||
selector *metav1.LabelSelector
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
f := framework.NewDefaultFramework("pv")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
/*
|
|
||||||
Test Setup
|
|
||||||
|
|
||||||
1. Create volume (vmdk)
|
|
||||||
2. Create PV with volume path for the vmdk.
|
|
||||||
3. Create PVC to bind with PV.
|
|
||||||
4. Create a POD using the PVC.
|
|
||||||
5. Verify Disk and Attached to the node.
|
|
||||||
*/
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
c = f.ClientSet
|
|
||||||
ns = f.Namespace.Name
|
|
||||||
clientPod = nil
|
|
||||||
pvc = nil
|
|
||||||
pv = nil
|
|
||||||
nodeInfo = GetReadySchedulableRandomNodeInfo(ctx, c)
|
|
||||||
|
|
||||||
volLabel = labels.Set{e2epv.VolumeSelectorKey: ns}
|
|
||||||
selector = metav1.SetAsLabelSelector(volLabel)
|
|
||||||
|
|
||||||
volumePath, err = nodeInfo.VSphere.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
nodeInfo.VSphere.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
|
|
||||||
})
|
|
||||||
pvConfig = e2epv.PersistentVolumeConfig{
|
|
||||||
NamePrefix: "vspherepv-",
|
|
||||||
Labels: volLabel,
|
|
||||||
PVSource: v1.PersistentVolumeSource{
|
|
||||||
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
|
|
||||||
VolumePath: volumePath,
|
|
||||||
FSType: "ext4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Prebind: nil,
|
|
||||||
}
|
|
||||||
emptyStorageClass := ""
|
|
||||||
pvcConfig = e2epv.PersistentVolumeClaimConfig{
|
|
||||||
Selector: selector,
|
|
||||||
StorageClassName: &emptyStorageClass,
|
|
||||||
}
|
|
||||||
ginkgo.By("Creating the PV and PVC")
|
|
||||||
pv, pvc, err = e2epv.CreatePVPVC(ctx, c, f.Timeouts, pvConfig, pvcConfig, ns, false)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pv.Name), "AfterEach: failed to delete PV ", pv.Name)
|
|
||||||
})
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns), "AfterEach: failed to delete PVC ", pvc.Name)
|
|
||||||
})
|
|
||||||
framework.ExpectNoError(e2epv.WaitOnPVandPVC(ctx, c, f.Timeouts, ns, pv, pvc))
|
|
||||||
|
|
||||||
ginkgo.By("Creating the Client Pod")
|
|
||||||
clientPod, err = e2epod.CreateClientPod(ctx, c, ns, pvc)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
node = clientPod.Spec.NodeName
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, clientPod), "AfterEach: failed to delete pod ", clientPod.Name)
|
|
||||||
})
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, volumePath, node), "wait for vsphere disk to detach")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.By("Verify disk should be attached to the node")
|
|
||||||
isAttached, err := diskIsAttached(ctx, volumePath, node)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if !isAttached {
|
|
||||||
framework.Failf("Disk %s is not attached with the node", volumePath)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("should test that deleting a PVC before the pod does not cause pod deletion to fail on vsphere volume detach", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Deleting the Claim")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns), "Failed to delete PVC ", pvc.Name)
|
|
||||||
pvc = nil
|
|
||||||
|
|
||||||
ginkgo.By("Deleting the Pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, clientPod), "Failed to delete pod ", clientPod.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Delete the PV and then the pod. Expect the pod to succeed in unmounting and detaching PD on delete.
|
|
||||||
|
|
||||||
Test Steps:
|
|
||||||
1. Delete PV.
|
|
||||||
2. Delete POD, POD deletion should succeed.
|
|
||||||
*/
|
|
||||||
ginkgo.It("should test that deleting the PV before the pod does not cause pod deletion to fail on vsphere volume detach", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Deleting the Persistent Volume")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pv.Name), "Failed to delete PV ", pv.Name)
|
|
||||||
pv = nil
|
|
||||||
|
|
||||||
ginkgo.By("Deleting the pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, clientPod), "Failed to delete pod ", clientPod.Name)
|
|
||||||
})
|
|
||||||
/*
|
|
||||||
This test verifies that a volume mounted to a pod remains mounted after a kubelet restarts.
|
|
||||||
Steps:
|
|
||||||
1. Write to the volume
|
|
||||||
2. Restart kubelet
|
|
||||||
3. Verify that written file is accessible after kubelet restart
|
|
||||||
*/
|
|
||||||
f.It("should test that a file written to the vsphere volume mount before kubelet restart can be read after restart", f.WithDisruptive(), func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessSSHKeyPresent()
|
|
||||||
utils.TestKubeletRestartsAndRestoresMount(ctx, c, f, clientPod, e2epod.VolumeMountPath1)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test verifies that a volume mounted to a pod that is deleted while the kubelet is down
|
|
||||||
unmounts volume when the kubelet returns.
|
|
||||||
|
|
||||||
Steps:
|
|
||||||
1. Verify volume is mounted on the node.
|
|
||||||
2. Stop kubelet.
|
|
||||||
3. Delete pod.
|
|
||||||
4. Start kubelet.
|
|
||||||
5. Verify that volume mount not to be found.
|
|
||||||
*/
|
|
||||||
f.It("should test that a vsphere volume mounted to a pod that is deleted while the kubelet is down unmounts when the kubelet returns", f.WithDisruptive(), func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessSSHKeyPresent()
|
|
||||||
utils.TestVolumeUnmountsFromDeletedPod(ctx, c, f, clientPod, e2epod.VolumeMountPath1)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test verifies that deleting the Namespace of a PVC and Pod causes the successful detach of Persistent Disk
|
|
||||||
|
|
||||||
Steps:
|
|
||||||
1. Delete Namespace.
|
|
||||||
2. Wait for namespace to get deleted. (Namespace deletion should trigger deletion of belonging pods)
|
|
||||||
3. Verify volume should be detached from the node.
|
|
||||||
*/
|
|
||||||
ginkgo.It("should test that deleting the Namespace of a PVC and Pod causes the successful detach of vsphere volume", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Deleting the Namespace")
|
|
||||||
err := c.CoreV1().Namespaces().Delete(ctx, ns, metav1.DeleteOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
err = framework.WaitForNamespacesDeleted(ctx, c, []string{ns}, 3*time.Minute)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verifying Persistent Disk detaches")
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, volumePath, node)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("PersistentVolumes", feature.Vsphere, feature.ReclaimPolicy, func() {
|
|
||||||
f := framework.NewDefaultFramework("persistentvolumereclaim")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
c clientset.Interface
|
|
||||||
ns string
|
|
||||||
volumePath string
|
|
||||||
pv *v1.PersistentVolume
|
|
||||||
pvc *v1.PersistentVolumeClaim
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
c = f.ClientSet
|
|
||||||
ns = f.Namespace.Name
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, c, f.Timeouts.NodeSchedulable))
|
|
||||||
})
|
|
||||||
|
|
||||||
f.Describe("persistentvolumereclaim:vsphere", feature.Vsphere, func() {
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
ginkgo.DeferCleanup(testCleanupVSpherePersistentVolumeReclaim, c, nodeInfo, ns, volumePath, pv, pvc)
|
|
||||||
Bootstrap(f)
|
|
||||||
nodeInfo = GetReadySchedulableRandomNodeInfo(ctx, c)
|
|
||||||
pv = nil
|
|
||||||
pvc = nil
|
|
||||||
volumePath = ""
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test verifies persistent volume should be deleted when reclaimPolicy on the PV is set to delete and
|
|
||||||
associated claim is deleted
|
|
||||||
|
|
||||||
Test Steps:
|
|
||||||
1. Create vmdk
|
|
||||||
2. Create PV Spec with volume path set to VMDK file created in Step-1, and PersistentVolumeReclaimPolicy is set to Delete
|
|
||||||
3. Create PVC with the storage request set to PV's storage capacity.
|
|
||||||
4. Wait for PV and PVC to bound.
|
|
||||||
5. Delete PVC
|
|
||||||
6. Verify PV is deleted automatically.
|
|
||||||
*/
|
|
||||||
ginkgo.It("should delete persistent volume when reclaimPolicy set to delete and associated claim is deleted", func(ctx context.Context) {
|
|
||||||
var err error
|
|
||||||
volumePath, pv, pvc, err = testSetupVSpherePersistentVolumeReclaim(ctx, c, nodeInfo, ns, v1.PersistentVolumeReclaimDelete)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
deletePVCAfterBind(ctx, c, ns, pvc, pv, f.Timeouts)
|
|
||||||
pvc = nil
|
|
||||||
|
|
||||||
ginkgo.By("verify pv is deleted")
|
|
||||||
err = e2epv.WaitForPersistentVolumeDeleted(ctx, c, pv.Name, 3*time.Second, 300*time.Second)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
pv = nil
|
|
||||||
volumePath = ""
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test Steps:
|
|
||||||
1. Create vmdk
|
|
||||||
2. Create PV Spec with volume path set to VMDK file created in Step-1, and PersistentVolumeReclaimPolicy is set to Delete
|
|
||||||
3. Create PVC with the storage request set to PV's storage capacity.
|
|
||||||
4. Wait for PV and PVC to bound.
|
|
||||||
5. Delete PVC.
|
|
||||||
6. Verify volume is attached to the node and volume is accessible in the pod.
|
|
||||||
7. Verify PV status should be failed.
|
|
||||||
8. Delete the pod.
|
|
||||||
9. Verify PV should be detached from the node and automatically deleted.
|
|
||||||
*/
|
|
||||||
ginkgo.It("should not detach and unmount PV when associated pvc with delete as reclaimPolicy is deleted when it is in use by the pod", func(ctx context.Context) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
volumePath, pv, pvc, err = testSetupVSpherePersistentVolumeReclaim(ctx, c, nodeInfo, ns, v1.PersistentVolumeReclaimDelete)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
// Wait for PV and PVC to Bind
|
|
||||||
framework.ExpectNoError(e2epv.WaitOnPVandPVC(ctx, c, f.Timeouts, ns, pv, pvc))
|
|
||||||
|
|
||||||
ginkgo.By("Creating the Pod")
|
|
||||||
pod, err := e2epod.CreateClientPod(ctx, c, ns, pvc)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting the Claim")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns), "Failed to delete PVC ", pvc.Name)
|
|
||||||
pvc = nil
|
|
||||||
|
|
||||||
// Verify PV is Present, after PVC is deleted and PV status should be Failed.
|
|
||||||
pv, err := c.CoreV1().PersistentVolumes().Get(ctx, pv.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
err = e2epv.WaitForPersistentVolumePhase(ctx, v1.VolumeFailed, c, pv.Name, 1*time.Second, 60*time.Second)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verify the volume is attached to the node")
|
|
||||||
isVolumeAttached, verifyDiskAttachedError := diskIsAttached(ctx, pv.Spec.VsphereVolume.VolumePath, pod.Spec.NodeName)
|
|
||||||
framework.ExpectNoError(verifyDiskAttachedError)
|
|
||||||
if !isVolumeAttached {
|
|
||||||
framework.Failf("Disk %s is not attached with the node %s", pv.Spec.VsphereVolume.VolumePath, pod.Spec.NodeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Verify the volume is accessible and available in the pod")
|
|
||||||
verifyVSphereVolumesAccessible(ctx, c, pod, []*v1.PersistentVolume{pv})
|
|
||||||
framework.Logf("Verified that Volume is accessible in the POD after deleting PV claim")
|
|
||||||
|
|
||||||
ginkgo.By("Deleting the Pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, pod), "Failed to delete pod ", pod.Name)
|
|
||||||
|
|
||||||
ginkgo.By("Verify PV is detached from the node after Pod is deleted")
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, pv.Spec.VsphereVolume.VolumePath, pod.Spec.NodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verify PV should be deleted automatically")
|
|
||||||
framework.ExpectNoError(e2epv.WaitForPersistentVolumeDeleted(ctx, c, pv.Name, 1*time.Second, 30*time.Second))
|
|
||||||
pv = nil
|
|
||||||
volumePath = ""
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test Verify persistent volume should be retained when reclaimPolicy on the PV is set to retain
|
|
||||||
and associated claim is deleted
|
|
||||||
|
|
||||||
Test Steps:
|
|
||||||
1. Create vmdk
|
|
||||||
2. Create PV Spec with volume path set to VMDK file created in Step-1, and PersistentVolumeReclaimPolicy is set to Retain
|
|
||||||
3. Create PVC with the storage request set to PV's storage capacity.
|
|
||||||
4. Wait for PV and PVC to bound.
|
|
||||||
5. Write some content in the volume.
|
|
||||||
6. Delete PVC
|
|
||||||
7. Verify PV is retained.
|
|
||||||
8. Delete retained PV.
|
|
||||||
9. Create PV Spec with the same volume path used in step 2.
|
|
||||||
10. Create PVC with the storage request set to PV's storage capacity.
|
|
||||||
11. Created POD using PVC created in Step 10 and verify volume content is matching.
|
|
||||||
*/
|
|
||||||
|
|
||||||
ginkgo.It("should retain persistent volume when reclaimPolicy set to retain when associated claim is deleted", func(ctx context.Context) {
|
|
||||||
var err error
|
|
||||||
var volumeFileContent = "hello from vsphere cloud provider, Random Content is :" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
|
||||||
|
|
||||||
volumePath, pv, pvc, err = testSetupVSpherePersistentVolumeReclaim(ctx, c, nodeInfo, ns, v1.PersistentVolumeReclaimRetain)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
writeContentToVSpherePV(ctx, c, f.Timeouts, pvc, volumeFileContent)
|
|
||||||
|
|
||||||
ginkgo.By("Delete PVC")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns), "Failed to delete PVC ", pvc.Name)
|
|
||||||
pvc = nil
|
|
||||||
|
|
||||||
ginkgo.By("Verify PV is retained")
|
|
||||||
framework.Logf("Waiting for PV %v to become Released", pv.Name)
|
|
||||||
err = e2epv.WaitForPersistentVolumePhase(ctx, v1.VolumeReleased, c, pv.Name, 3*time.Second, 300*time.Second)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pv.Name), "Failed to delete PV ", pv.Name)
|
|
||||||
|
|
||||||
ginkgo.By("Creating the PV for same volume path")
|
|
||||||
pv = getVSpherePersistentVolumeSpec(volumePath, v1.PersistentVolumeReclaimRetain, nil)
|
|
||||||
pv, err = c.CoreV1().PersistentVolumes().Create(ctx, pv, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("creating the pvc")
|
|
||||||
pvc = getVSpherePersistentVolumeClaimSpec(ns, nil)
|
|
||||||
pvc, err = c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("wait for the pv and pvc to bind")
|
|
||||||
framework.ExpectNoError(e2epv.WaitOnPVandPVC(ctx, c, f.Timeouts, ns, pv, pvc))
|
|
||||||
verifyContentOfVSpherePV(ctx, c, f.Timeouts, pvc, volumeFileContent)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Test Setup for persistentvolumereclaim tests for vSphere Provider
|
|
||||||
func testSetupVSpherePersistentVolumeReclaim(ctx context.Context, c clientset.Interface, nodeInfo *NodeInfo, ns string, persistentVolumeReclaimPolicy v1.PersistentVolumeReclaimPolicy) (volumePath string, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim, err error) {
|
|
||||||
ginkgo.By("running testSetupVSpherePersistentVolumeReclaim")
|
|
||||||
ginkgo.By("creating vmdk")
|
|
||||||
volumePath, err = nodeInfo.VSphere.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ginkgo.By("creating the pv")
|
|
||||||
pv = getVSpherePersistentVolumeSpec(volumePath, persistentVolumeReclaimPolicy, nil)
|
|
||||||
pv, err = c.CoreV1().PersistentVolumes().Create(ctx, pv, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ginkgo.By("creating the pvc")
|
|
||||||
pvc = getVSpherePersistentVolumeClaimSpec(ns, nil)
|
|
||||||
pvc, err = c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test Cleanup for persistentvolumereclaim tests for vSphere Provider
|
|
||||||
func testCleanupVSpherePersistentVolumeReclaim(ctx context.Context, c clientset.Interface, nodeInfo *NodeInfo, ns string, volumePath string, pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) {
|
|
||||||
ginkgo.By("running testCleanupVSpherePersistentVolumeReclaim")
|
|
||||||
if len(volumePath) > 0 {
|
|
||||||
err := nodeInfo.VSphere.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
if pv != nil {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pv.Name), "Failed to delete PV ", pv.Name)
|
|
||||||
}
|
|
||||||
if pvc != nil {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns), "Failed to delete PVC ", pvc.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// func to wait until PV and PVC bind and once bind completes, delete the PVC
|
|
||||||
func deletePVCAfterBind(ctx context.Context, c clientset.Interface, ns string, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume, timeouts *framework.TimeoutContext) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ginkgo.By("wait for the pv and pvc to bind")
|
|
||||||
framework.ExpectNoError(e2epv.WaitOnPVandPVC(ctx, c, timeouts, ns, pv, pvc))
|
|
||||||
|
|
||||||
ginkgo.By("delete pvc")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvc.Name, ns), "Failed to delete PVC ", pvc.Name)
|
|
||||||
_, err = c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvc.Name, metav1.GetOptions{})
|
|
||||||
if !apierrors.IsNotFound(err) {
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is a function test for Selector-Label Volume Binding Feature
|
|
||||||
Test verifies volume with the matching label is bounded with the PVC.
|
|
||||||
|
|
||||||
Test Steps
|
|
||||||
----------
|
|
||||||
1. Create VMDK.
|
|
||||||
2. Create pv with label volume-type:ssd, volume path set to vmdk created in previous step, and PersistentVolumeReclaimPolicy is set to Delete.
|
|
||||||
3. Create PVC (pvcVvol) with label selector to match with volume-type:vvol
|
|
||||||
4. Create PVC (pvcSsd) with label selector to match with volume-type:ssd
|
|
||||||
5. Wait and verify pvSsd is bound with PV.
|
|
||||||
6. Verify Status of pvcVvol is still pending.
|
|
||||||
7. Delete pvcSsd.
|
|
||||||
8. verify associated pv is also deleted.
|
|
||||||
9. delete pvcVvol
|
|
||||||
*/
|
|
||||||
var _ = utils.SIGDescribe("PersistentVolumes", feature.Vsphere, feature.LabelSelector, func() {
|
|
||||||
f := framework.NewDefaultFramework("pvclabelselector")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
c clientset.Interface
|
|
||||||
ns string
|
|
||||||
pvSsd *v1.PersistentVolume
|
|
||||||
pvcSsd *v1.PersistentVolumeClaim
|
|
||||||
pvcVvol *v1.PersistentVolumeClaim
|
|
||||||
volumePath string
|
|
||||||
ssdlabels map[string]string
|
|
||||||
vvollabels map[string]string
|
|
||||||
err error
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
c = f.ClientSet
|
|
||||||
ns = f.Namespace.Name
|
|
||||||
Bootstrap(f)
|
|
||||||
nodeInfo = GetReadySchedulableRandomNodeInfo(ctx, c)
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, c, f.Timeouts.NodeSchedulable))
|
|
||||||
ssdlabels = make(map[string]string)
|
|
||||||
ssdlabels["volume-type"] = "ssd"
|
|
||||||
vvollabels = make(map[string]string)
|
|
||||||
vvollabels["volume-type"] = "vvol"
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
f.Describe("Selector-Label Volume Binding:vsphere", feature.Vsphere, func() {
|
|
||||||
ginkgo.AfterEach(func(ctx context.Context) {
|
|
||||||
ginkgo.By("Running clean up actions")
|
|
||||||
if framework.ProviderIs("vsphere") {
|
|
||||||
testCleanupVSpherePVClabelselector(ctx, c, ns, nodeInfo, volumePath, pvSsd, pvcSsd, pvcVvol)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ginkgo.It("should bind volume with claim for given label", func(ctx context.Context) {
|
|
||||||
volumePath, pvSsd, pvcSsd, pvcVvol, err = testSetupVSpherePVClabelselector(ctx, c, nodeInfo, ns, ssdlabels, vvollabels)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("wait for the pvcSsd to bind with pvSsd")
|
|
||||||
framework.ExpectNoError(e2epv.WaitOnPVandPVC(ctx, c, f.Timeouts, ns, pvSsd, pvcSsd))
|
|
||||||
|
|
||||||
ginkgo.By("Verify status of pvcVvol is pending")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimPending, c, ns, pvcVvol.Name, 3*time.Second, 300*time.Second)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("delete pvcSsd")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvcSsd.Name, ns), "Failed to delete PVC ", pvcSsd.Name)
|
|
||||||
|
|
||||||
ginkgo.By("verify pvSsd is deleted")
|
|
||||||
err = e2epv.WaitForPersistentVolumeDeleted(ctx, c, pvSsd.Name, 3*time.Second, 300*time.Second)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePath = ""
|
|
||||||
|
|
||||||
ginkgo.By("delete pvcVvol")
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvcVvol.Name, ns), "Failed to delete PVC ", pvcVvol.Name)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func testSetupVSpherePVClabelselector(ctx context.Context, c clientset.Interface, nodeInfo *NodeInfo, ns string, ssdlabels map[string]string, vvollabels map[string]string) (volumePath string, pvSsd *v1.PersistentVolume, pvcSsd *v1.PersistentVolumeClaim, pvcVvol *v1.PersistentVolumeClaim, err error) {
|
|
||||||
ginkgo.By("creating vmdk")
|
|
||||||
volumePath, err = nodeInfo.VSphere.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("creating the pv with label volume-type:ssd")
|
|
||||||
pvSsd = getVSpherePersistentVolumeSpec(volumePath, v1.PersistentVolumeReclaimDelete, ssdlabels)
|
|
||||||
pvSsd, err = c.CoreV1().PersistentVolumes().Create(ctx, pvSsd, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("creating pvc with label selector to match with volume-type:vvol")
|
|
||||||
pvcVvol = getVSpherePersistentVolumeClaimSpec(ns, vvollabels)
|
|
||||||
pvcVvol, err = c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvcVvol, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("creating pvc with label selector to match with volume-type:ssd")
|
|
||||||
pvcSsd = getVSpherePersistentVolumeClaimSpec(ns, ssdlabels)
|
|
||||||
pvcSsd, err = c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvcSsd, metav1.CreateOptions{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCleanupVSpherePVClabelselector(ctx context.Context, c clientset.Interface, ns string, nodeInfo *NodeInfo, volumePath string, pvSsd *v1.PersistentVolume, pvcSsd *v1.PersistentVolumeClaim, pvcVvol *v1.PersistentVolumeClaim) {
|
|
||||||
ginkgo.By("running testCleanupVSpherePVClabelselector")
|
|
||||||
if len(volumePath) > 0 {
|
|
||||||
nodeInfo.VSphere.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
|
|
||||||
}
|
|
||||||
if pvcSsd != nil {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvcSsd.Name, ns), "Failed to delete PVC ", pvcSsd.Name)
|
|
||||||
}
|
|
||||||
if pvcVvol != nil {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, pvcVvol.Name, ns), "Failed to delete PVC ", pvcVvol.Name)
|
|
||||||
}
|
|
||||||
if pvSsd != nil {
|
|
||||||
framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pvSsd.Name), "Failed to delete PV ", pvSsd.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi"
|
|
||||||
"github.com/vmware/govmomi/find"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
volDir = "kubevols"
|
|
||||||
defaultDiskCapacityKB = 2097152
|
|
||||||
defaultDiskFormat = "thin"
|
|
||||||
defaultSCSIControllerType = "lsiLogic"
|
|
||||||
virtualMachineType = "VirtualMachine"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VSphere represents a vSphere instance where one or more kubernetes nodes are running.
|
|
||||||
type VSphere struct {
|
|
||||||
Config *Config
|
|
||||||
Client *govmomi.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeOptions specifies various options for a volume.
|
|
||||||
type VolumeOptions struct {
|
|
||||||
Name string
|
|
||||||
CapacityKB int
|
|
||||||
DiskFormat string
|
|
||||||
SCSIControllerType string
|
|
||||||
Datastore string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatacenter returns the DataCenter Object for the given datacenterPath
|
|
||||||
func (vs *VSphere) GetDatacenter(ctx context.Context, datacenterPath string) (*object.Datacenter, error) {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
finder := find.NewFinder(vs.Client.Client, false)
|
|
||||||
return finder.Datacenter(ctx, datacenterPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatacenterFromObjectReference returns the DataCenter Object for the given datacenter reference
|
|
||||||
func (vs *VSphere) GetDatacenterFromObjectReference(ctx context.Context, dc object.Reference) *object.Datacenter {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
return object.NewDatacenter(vs.Client.Client, dc.Reference())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllDatacenter returns all the DataCenter Objects
|
|
||||||
func (vs *VSphere) GetAllDatacenter(ctx context.Context) ([]*object.Datacenter, error) {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
finder := find.NewFinder(vs.Client.Client, false)
|
|
||||||
return finder.DatacenterList(ctx, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVMByUUID returns the VM object Reference from the given vmUUID
|
|
||||||
func (vs *VSphere) GetVMByUUID(ctx context.Context, vmUUID string, dc object.Reference) (object.Reference, error) {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
datacenter := vs.GetDatacenterFromObjectReference(ctx, dc)
|
|
||||||
s := object.NewSearchIndex(vs.Client.Client)
|
|
||||||
vmUUID = strings.ToLower(strings.TrimSpace(vmUUID))
|
|
||||||
return s.FindByUuid(ctx, datacenter, vmUUID, true, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHostFromVMReference returns host object reference of the host on which the specified VM resides
|
|
||||||
func (vs *VSphere) GetHostFromVMReference(ctx context.Context, vm types.ManagedObjectReference) types.ManagedObjectReference {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
var vmMo mo.VirtualMachine
|
|
||||||
vs.Client.RetrieveOne(ctx, vm, []string{"summary.runtime.host"}, &vmMo)
|
|
||||||
host := *vmMo.Summary.Runtime.Host
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoresMountedOnHost returns the datastore references of all the datastores mounted on the specified host
|
|
||||||
func (vs *VSphere) GetDatastoresMountedOnHost(ctx context.Context, host types.ManagedObjectReference) []types.ManagedObjectReference {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
var hostMo mo.HostSystem
|
|
||||||
vs.Client.RetrieveOne(ctx, host, []string{"datastore"}, &hostMo)
|
|
||||||
return hostMo.Datastore
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatastoreRefFromName returns the datastore reference of the specified datastore
|
|
||||||
func (vs *VSphere) GetDatastoreRefFromName(ctx context.Context, dc object.Reference, datastoreName string) (types.ManagedObjectReference, error) {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
datacenter := object.NewDatacenter(vs.Client.Client, dc.Reference())
|
|
||||||
finder := find.NewFinder(vs.Client.Client, false)
|
|
||||||
finder.SetDatacenter(datacenter)
|
|
||||||
datastore, err := finder.Datastore(ctx, datastoreName)
|
|
||||||
return datastore.Reference(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFolderByPath gets the Folder Object Reference from the given folder path
|
|
||||||
// folderPath should be the full path to folder
|
|
||||||
func (vs *VSphere) GetFolderByPath(ctx context.Context, dc object.Reference, folderPath string) (vmFolderMor types.ManagedObjectReference, err error) {
|
|
||||||
Connect(ctx, vs)
|
|
||||||
datacenter := object.NewDatacenter(vs.Client.Client, dc.Reference())
|
|
||||||
finder := find.NewFinder(datacenter.Client(), false)
|
|
||||||
finder.SetDatacenter(datacenter)
|
|
||||||
vmFolder, err := finder.Folder(ctx, folderPath)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to get the folder reference for %s. err: %+v", folderPath, err)
|
|
||||||
return vmFolderMor, err
|
|
||||||
}
|
|
||||||
return vmFolder.Reference(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateVolume creates a vsphere volume using given volume parameters specified in VolumeOptions.
|
|
||||||
// If volume is created successfully the canonical disk path is returned else error is returned.
|
|
||||||
func (vs *VSphere) CreateVolume(volumeOptions *VolumeOptions, dataCenterRef types.ManagedObjectReference) (string, error) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
Connect(ctx, vs)
|
|
||||||
datacenter := object.NewDatacenter(vs.Client.Client, dataCenterRef)
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
directoryAlreadyPresent = false
|
|
||||||
)
|
|
||||||
if datacenter == nil {
|
|
||||||
return "", fmt.Errorf("datacenter is nil")
|
|
||||||
}
|
|
||||||
vs.initVolumeOptions(volumeOptions)
|
|
||||||
finder := find.NewFinder(datacenter.Client(), false)
|
|
||||||
finder.SetDatacenter(datacenter)
|
|
||||||
ds, err := finder.Datastore(ctx, volumeOptions.Datastore)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Failed while searching for datastore: %s. err: %+v", volumeOptions.Datastore, err)
|
|
||||||
}
|
|
||||||
directoryPath := filepath.Clean(ds.Path(volDir)) + "/"
|
|
||||||
fileManager := object.NewFileManager(ds.Client())
|
|
||||||
err = fileManager.MakeDirectory(ctx, directoryPath, datacenter, false)
|
|
||||||
if err != nil {
|
|
||||||
if soap.IsSoapFault(err) {
|
|
||||||
soapFault := soap.ToSoapFault(err)
|
|
||||||
if _, ok := soapFault.VimFault().(types.FileAlreadyExists); ok {
|
|
||||||
directoryAlreadyPresent = true
|
|
||||||
framework.Logf("Directory with the path %+q is already present", directoryPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !directoryAlreadyPresent {
|
|
||||||
framework.Logf("Cannot create dir %#v. err %s", directoryPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
framework.Logf("Created dir with path as %+q", directoryPath)
|
|
||||||
vmdkPath := directoryPath + volumeOptions.Name + ".vmdk"
|
|
||||||
|
|
||||||
// Create a virtual disk manager
|
|
||||||
vdm := object.NewVirtualDiskManager(ds.Client())
|
|
||||||
// Create specification for new virtual disk
|
|
||||||
vmDiskSpec := &types.FileBackedVirtualDiskSpec{
|
|
||||||
VirtualDiskSpec: types.VirtualDiskSpec{
|
|
||||||
AdapterType: volumeOptions.SCSIControllerType,
|
|
||||||
DiskType: volumeOptions.DiskFormat,
|
|
||||||
},
|
|
||||||
CapacityKb: int64(volumeOptions.CapacityKB),
|
|
||||||
}
|
|
||||||
// Create virtual disk
|
|
||||||
task, err := vdm.CreateVirtualDisk(ctx, vmdkPath, datacenter, vmDiskSpec)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to create virtual disk: %s. err: %+v", vmdkPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
taskInfo, err := task.WaitForResult(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to complete virtual disk creation: %s. err: %+v", vmdkPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
volumePath := taskInfo.Result.(string)
|
|
||||||
canonicalDiskPath, err := getCanonicalVolumePath(ctx, datacenter, volumePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return canonicalDiskPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteVolume deletes the vmdk file specified in the volumePath.
|
|
||||||
// if an error is encountered while deleting volume, error is returned.
|
|
||||||
func (vs *VSphere) DeleteVolume(volumePath string, dataCenterRef types.ManagedObjectReference) error {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
Connect(ctx, vs)
|
|
||||||
|
|
||||||
datacenter := object.NewDatacenter(vs.Client.Client, dataCenterRef)
|
|
||||||
virtualDiskManager := object.NewVirtualDiskManager(datacenter.Client())
|
|
||||||
diskPath := removeStorageClusterORFolderNameFromVDiskPath(volumePath)
|
|
||||||
// Delete virtual disk
|
|
||||||
task, err := virtualDiskManager.DeleteVirtualDisk(ctx, diskPath, datacenter)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to delete virtual disk. err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = task.Wait(ctx)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to delete virtual disk. err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsVMPresent checks if VM with the name specified in the vmName argument, is present in the vCenter inventory.
|
|
||||||
// if VM is present, function returns true else false.
|
|
||||||
func (vs *VSphere) IsVMPresent(vmName string, dataCenterRef types.ManagedObjectReference) (isVMPresent bool, err error) {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
Connect(ctx, vs)
|
|
||||||
folderMor, err := vs.GetFolderByPath(ctx, dataCenterRef, vs.Config.Folder)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vmFolder := object.NewFolder(vs.Client.Client, folderMor)
|
|
||||||
vmFoldersChildren, err := vmFolder.Children(ctx)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to get children from Folder: %s. err: %+v", vmFolder.InventoryPath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, vmFoldersChild := range vmFoldersChildren {
|
|
||||||
if vmFoldersChild.Reference().Type == virtualMachineType {
|
|
||||||
if object.NewVirtualMachine(vs.Client.Client, vmFoldersChild.Reference()).Name() == vmName {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// initVolumeOptions function sets default values for volumeOptions parameters if not set
|
|
||||||
func (vs *VSphere) initVolumeOptions(volumeOptions *VolumeOptions) {
|
|
||||||
if volumeOptions == nil {
|
|
||||||
volumeOptions = &VolumeOptions{}
|
|
||||||
}
|
|
||||||
if volumeOptions.Datastore == "" {
|
|
||||||
volumeOptions.Datastore = vs.Config.DefaultDatastore
|
|
||||||
}
|
|
||||||
if volumeOptions.CapacityKB == 0 {
|
|
||||||
volumeOptions.CapacityKB = defaultDiskCapacityKB
|
|
||||||
}
|
|
||||||
if volumeOptions.Name == "" {
|
|
||||||
volumeOptions.Name = "e2e-vmdk-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
|
||||||
}
|
|
||||||
if volumeOptions.DiskFormat == "" {
|
|
||||||
volumeOptions.DiskFormat = defaultDiskFormat
|
|
||||||
}
|
|
||||||
if volumeOptions.SCSIControllerType == "" {
|
|
||||||
volumeOptions.SCSIControllerType = defaultSCSIControllerType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VSphereCSIMigrationEnabled is the environment variables to help
|
|
||||||
// determine test verification flow.
|
|
||||||
const VSphereCSIMigrationEnabled = "VSPHERE_CSI_MIGRATION_ENABLED"
|
|
||||||
|
|
||||||
// environment variables related to datastore parameters
|
|
||||||
const (
|
|
||||||
SPBMPolicyName = "VSPHERE_SPBM_POLICY_NAME"
|
|
||||||
StorageClassDatastoreName = "VSPHERE_DATASTORE"
|
|
||||||
SecondSharedDatastore = "VSPHERE_SECOND_SHARED_DATASTORE"
|
|
||||||
KubernetesClusterName = "VSPHERE_KUBERNETES_CLUSTER"
|
|
||||||
SPBMTagPolicy = "VSPHERE_SPBM_TAG_POLICY"
|
|
||||||
VCPClusterDatastore = "CLUSTER_DATASTORE"
|
|
||||||
SPBMPolicyDataStoreCluster = "VSPHERE_SPBM_POLICY_DS_CLUSTER"
|
|
||||||
)
|
|
||||||
|
|
||||||
// environment variables used for scaling tests
|
|
||||||
const (
|
|
||||||
VCPScaleVolumeCount = "VCP_SCALE_VOLUME_COUNT"
|
|
||||||
VCPScaleVolumesPerPod = "VCP_SCALE_VOLUME_PER_POD"
|
|
||||||
VCPScaleInstances = "VCP_SCALE_INSTANCES"
|
|
||||||
)
|
|
||||||
|
|
||||||
// environment variables used for stress tests
|
|
||||||
const (
|
|
||||||
VCPStressInstances = "VCP_STRESS_INSTANCES"
|
|
||||||
VCPStressIterations = "VCP_STRESS_ITERATIONS"
|
|
||||||
)
|
|
||||||
|
|
||||||
// environment variables used for performance tests
|
|
||||||
const (
|
|
||||||
VCPPerfVolumeCount = "VCP_PERF_VOLUME_COUNT"
|
|
||||||
VCPPerfVolumesPerPod = "VCP_PERF_VOLUME_PER_POD"
|
|
||||||
VCPPerfIterations = "VCP_PERF_ITERATIONS"
|
|
||||||
)
|
|
||||||
|
|
||||||
// environment variables used for zone tests
|
|
||||||
const (
|
|
||||||
VCPZoneVsanDatastore1 = "VCP_ZONE_VSANDATASTORE1"
|
|
||||||
VCPZoneVsanDatastore2 = "VCP_ZONE_VSANDATASTORE2"
|
|
||||||
VCPZoneLocalDatastore = "VCP_ZONE_LOCALDATASTORE"
|
|
||||||
VCPZoneCompatPolicyName = "VCP_ZONE_COMPATPOLICY_NAME"
|
|
||||||
VCPZoneNonCompatPolicyName = "VCP_ZONE_NONCOMPATPOLICY_NAME"
|
|
||||||
VCPZoneA = "VCP_ZONE_A"
|
|
||||||
VCPZoneB = "VCP_ZONE_B"
|
|
||||||
VCPZoneC = "VCP_ZONE_C"
|
|
||||||
VCPZoneD = "VCP_ZONE_D"
|
|
||||||
VCPInvalidZone = "VCP_INVALID_ZONE"
|
|
||||||
)
|
|
||||||
|
|
||||||
// storage class parameters
|
|
||||||
const (
|
|
||||||
Datastore = "datastore"
|
|
||||||
PolicyDiskStripes = "diskStripes"
|
|
||||||
PolicyHostFailuresToTolerate = "hostFailuresToTolerate"
|
|
||||||
PolicyCacheReservation = "cacheReservation"
|
|
||||||
PolicyObjectSpaceReservation = "objectSpaceReservation"
|
|
||||||
PolicyIopsLimit = "iopsLimit"
|
|
||||||
DiskFormat = "diskformat"
|
|
||||||
SpbmStoragePolicy = "storagepolicyname"
|
|
||||||
)
|
|
||||||
|
|
||||||
// test values for storage class parameters
|
|
||||||
const (
|
|
||||||
ThinDisk = "thin"
|
|
||||||
BronzeStoragePolicy = "bronze"
|
|
||||||
HostFailuresToTolerateCapabilityVal = "0"
|
|
||||||
CacheReservationCapabilityVal = "20"
|
|
||||||
DiskStripesCapabilityVal = "1"
|
|
||||||
ObjectSpaceReservationCapabilityVal = "30"
|
|
||||||
IopsLimitCapabilityVal = "100"
|
|
||||||
StripeWidthCapabilityVal = "2"
|
|
||||||
DiskStripesCapabilityInvalidVal = "14"
|
|
||||||
HostFailuresToTolerateCapabilityInvalidVal = "4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetAndExpectStringEnvVar returns the string value of an environment variable or fails if
|
|
||||||
// the variable is not set
|
|
||||||
func GetAndExpectStringEnvVar(varName string) string {
|
|
||||||
varValue := os.Getenv(varName)
|
|
||||||
gomega.Expect(varValue).NotTo(gomega.BeEmpty(), "ENV "+varName+" is not set")
|
|
||||||
return varValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAndExpectIntEnvVar returns the integer value of an environment variable or fails if
|
|
||||||
// the variable is not set
|
|
||||||
func GetAndExpectIntEnvVar(varName string) int {
|
|
||||||
varValue := GetAndExpectStringEnvVar(varName)
|
|
||||||
varIntValue, err := strconv.Atoi(varValue)
|
|
||||||
framework.ExpectNoError(err, "Error Parsing "+varName)
|
|
||||||
return varIntValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAndExpectBoolEnvVar returns the bool value of an environment variable
|
|
||||||
// if environment variable is not set return false
|
|
||||||
func GetAndExpectBoolEnvVar(varName string) bool {
|
|
||||||
varValue := os.Getenv(varName)
|
|
||||||
if varValue == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
varBoolValue, err := strconv.ParseBool(varValue)
|
|
||||||
framework.ExpectNoError(err, "Error Parsing "+varName)
|
|
||||||
return varBoolValue
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Perform vsphere volume life cycle management at scale based on user configurable value for number of volumes.
|
|
||||||
The following actions will be performed as part of this test.
|
|
||||||
|
|
||||||
1. Create Storage Classes of 4 Categories (Default, SC with Non Default Datastore, SC with SPBM Policy, SC with VSAN Storage Capabilities.)
|
|
||||||
2. Read VCP_SCALE_VOLUME_COUNT, VCP_SCALE_INSTANCES, VCP_SCALE_VOLUMES_PER_POD, VSPHERE_SPBM_POLICY_NAME, VSPHERE_DATASTORE from System Environment.
|
|
||||||
3. Launch VCP_SCALE_INSTANCES goroutine for creating VCP_SCALE_VOLUME_COUNT volumes. Each goroutine is responsible for create/attach of VCP_SCALE_VOLUME_COUNT/VCP_SCALE_INSTANCES volumes.
|
|
||||||
4. Read VCP_SCALE_VOLUMES_PER_POD from System Environment. Each pod will be have VCP_SCALE_VOLUMES_PER_POD attached to it.
|
|
||||||
5. Once all the go routines are completed, we delete all the pods and volumes.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
NodeLabelKey = "vsphere_e2e_label"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeSelector holds
|
|
||||||
type NodeSelector struct {
|
|
||||||
labelKey string
|
|
||||||
labelValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("vcp at scale", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("vcp-at-scale")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
nodeSelectorList []*NodeSelector
|
|
||||||
volumeCount int
|
|
||||||
numberOfInstances int
|
|
||||||
volumesPerPod int
|
|
||||||
policyName string
|
|
||||||
datastoreName string
|
|
||||||
nodeVolumeMapChan chan map[string][]string
|
|
||||||
nodes *v1.NodeList
|
|
||||||
scNames = []string{storageclass1, storageclass2, storageclass3, storageclass4}
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
nodeVolumeMapChan = make(chan map[string][]string)
|
|
||||||
|
|
||||||
// Read the environment variables
|
|
||||||
volumeCount = GetAndExpectIntEnvVar(VCPScaleVolumeCount)
|
|
||||||
volumesPerPod = GetAndExpectIntEnvVar(VCPScaleVolumesPerPod)
|
|
||||||
|
|
||||||
numberOfInstances = GetAndExpectIntEnvVar(VCPScaleInstances)
|
|
||||||
if numberOfInstances > 5 {
|
|
||||||
framework.Failf("Maximum 5 instances allowed, got instead: %v", numberOfInstances)
|
|
||||||
}
|
|
||||||
if numberOfInstances > volumeCount {
|
|
||||||
framework.Failf("Number of instances: %v cannot be greater than volume count: %v", numberOfInstances, volumeCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
policyName = GetAndExpectStringEnvVar(SPBMPolicyName)
|
|
||||||
datastoreName = GetAndExpectStringEnvVar(StorageClassDatastoreName)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
nodes, err = e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if len(nodes.Items) < 2 {
|
|
||||||
e2eskipper.Skipf("Requires at least %d nodes (not %d)", 2, len(nodes.Items))
|
|
||||||
}
|
|
||||||
// Verify volume count specified by the user can be satisfied
|
|
||||||
if volumeCount > volumesPerNode*len(nodes.Items) {
|
|
||||||
e2eskipper.Skipf("Cannot attach %d volumes to %d nodes. Maximum volumes that can be attached on %d nodes is %d", volumeCount, len(nodes.Items), len(nodes.Items), volumesPerNode*len(nodes.Items))
|
|
||||||
}
|
|
||||||
nodeSelectorList = createNodeLabels(client, namespace, nodes)
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
for _, node := range nodes.Items {
|
|
||||||
e2enode.RemoveLabelOffNode(client, node.Name, NodeLabelKey)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("vsphere scale tests", func(ctx context.Context) {
|
|
||||||
var pvcClaimList []string
|
|
||||||
nodeVolumeMap := make(map[string][]string)
|
|
||||||
// Volumes will be provisioned with each different types of Storage Class
|
|
||||||
scArrays := make([]*storagev1.StorageClass, len(scNames))
|
|
||||||
for index, scname := range scNames {
|
|
||||||
// Create vSphere Storage Class
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating Storage Class : %q", scname))
|
|
||||||
var sc *storagev1.StorageClass
|
|
||||||
scParams := make(map[string]string)
|
|
||||||
var err error
|
|
||||||
switch scname {
|
|
||||||
case storageclass1:
|
|
||||||
scParams = nil
|
|
||||||
case storageclass2:
|
|
||||||
scParams[PolicyHostFailuresToTolerate] = "1"
|
|
||||||
case storageclass3:
|
|
||||||
scParams[SpbmStoragePolicy] = policyName
|
|
||||||
case storageclass4:
|
|
||||||
scParams[Datastore] = datastoreName
|
|
||||||
}
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(scname, scParams, nil, ""), metav1.CreateOptions{})
|
|
||||||
gomega.Expect(sc).NotTo(gomega.BeNil(), "Storage class is empty")
|
|
||||||
framework.ExpectNoError(err, "Failed to create storage class")
|
|
||||||
ginkgo.DeferCleanup(client.StorageV1().StorageClasses().Delete, scname, metav1.DeleteOptions{})
|
|
||||||
scArrays[index] = sc
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeCountPerInstance := volumeCount / numberOfInstances
|
|
||||||
for instanceCount := 0; instanceCount < numberOfInstances; instanceCount++ {
|
|
||||||
if instanceCount == numberOfInstances-1 {
|
|
||||||
volumeCountPerInstance = volumeCount
|
|
||||||
}
|
|
||||||
volumeCount = volumeCount - volumeCountPerInstance
|
|
||||||
go VolumeCreateAndAttach(ctx, f, scArrays, volumeCountPerInstance, volumesPerPod, nodeSelectorList, nodeVolumeMapChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the list of all volumes attached to each node from the go routines by reading the data from the channel
|
|
||||||
for instanceCount := 0; instanceCount < numberOfInstances; instanceCount++ {
|
|
||||||
for node, volumeList := range <-nodeVolumeMapChan {
|
|
||||||
nodeVolumeMap[node] = append(nodeVolumeMap[node], volumeList...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
podList, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
|
||||||
framework.ExpectNoError(err, "Failed to list pods")
|
|
||||||
for _, pod := range podList.Items {
|
|
||||||
pvcClaimList = append(pvcClaimList, getClaimsForPod(&pod, volumesPerPod)...)
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
err = e2epod.DeletePodWithWait(ctx, client, &pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
ginkgo.By("Waiting for volumes to be detached from the node")
|
|
||||||
err = waitForVSphereDisksToDetach(ctx, nodeVolumeMap)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
for _, pvcClaim := range pvcClaimList {
|
|
||||||
err = e2epv.DeletePersistentVolumeClaim(ctx, client, pvcClaim, namespace)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get PVC claims for the pod
|
|
||||||
func getClaimsForPod(pod *v1.Pod, volumesPerPod int) []string {
|
|
||||||
pvcClaimList := make([]string, volumesPerPod)
|
|
||||||
for i, volumespec := range pod.Spec.Volumes {
|
|
||||||
if volumespec.PersistentVolumeClaim != nil {
|
|
||||||
pvcClaimList[i] = volumespec.PersistentVolumeClaim.ClaimName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pvcClaimList
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeCreateAndAttach peforms create and attach operations of vSphere persistent volumes at scale
|
|
||||||
func VolumeCreateAndAttach(ctx context.Context, f *framework.Framework, sc []*storagev1.StorageClass, volumeCountPerInstance int, volumesPerPod int, nodeSelectorList []*NodeSelector, nodeVolumeMapChan chan map[string][]string) {
|
|
||||||
defer ginkgo.GinkgoRecover()
|
|
||||||
client := f.ClientSet
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
nodeVolumeMap := make(map[string][]string)
|
|
||||||
nodeSelectorIndex := 0
|
|
||||||
for index := 0; index < volumeCountPerInstance; index = index + volumesPerPod {
|
|
||||||
if (volumeCountPerInstance - index) < volumesPerPod {
|
|
||||||
volumesPerPod = volumeCountPerInstance - index
|
|
||||||
}
|
|
||||||
pvclaims := make([]*v1.PersistentVolumeClaim, volumesPerPod)
|
|
||||||
for i := 0; i < volumesPerPod; i++ {
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", sc[index%len(sc)]))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
pvclaims[i] = pvclaim
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod to attach PV to the node")
|
|
||||||
nodeSelector := nodeSelectorList[nodeSelectorIndex%len(nodeSelectorList)]
|
|
||||||
// Create pod to attach Volume to Node
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, map[string]string{nodeSelector.labelKey: nodeSelector.labelValue}, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
for _, pv := range persistentvolumes {
|
|
||||||
nodeVolumeMap[pod.Spec.NodeName] = append(nodeVolumeMap[pod.Spec.NodeName], pv.Spec.VsphereVolume.VolumePath)
|
|
||||||
}
|
|
||||||
ginkgo.By("Verify the volume is accessible and available in the pod")
|
|
||||||
verifyVSphereVolumesAccessible(ctx, client, pod, persistentvolumes)
|
|
||||||
nodeSelectorIndex++
|
|
||||||
}
|
|
||||||
nodeVolumeMapChan <- nodeVolumeMap
|
|
||||||
close(nodeVolumeMapChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNodeLabels(client clientset.Interface, namespace string, nodes *v1.NodeList) []*NodeSelector {
|
|
||||||
var nodeSelectorList []*NodeSelector
|
|
||||||
for i, node := range nodes.Items {
|
|
||||||
labelVal := "vsphere_e2e_" + strconv.Itoa(i)
|
|
||||||
nodeSelector := &NodeSelector{
|
|
||||||
labelKey: NodeLabelKey,
|
|
||||||
labelValue: labelVal,
|
|
||||||
}
|
|
||||||
nodeSelectorList = append(nodeSelectorList, nodeSelector)
|
|
||||||
e2enode.AddOrUpdateLabelOnNode(client, node.Name, NodeLabelKey, labelVal)
|
|
||||||
}
|
|
||||||
return nodeSelectorList
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
e2estatefulset "k8s.io/kubernetes/test/e2e/framework/statefulset"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test performs following operations
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create a storage class with thin diskformat.
|
|
||||||
2. Create nginx service.
|
|
||||||
3. Create nginx statefulsets with 3 replicas.
|
|
||||||
4. Wait until all Pods are ready and PVCs are bounded with PV.
|
|
||||||
5. Verify volumes are accessible in all statefulsets pods with creating empty file.
|
|
||||||
6. Scale down statefulsets to 2 replicas.
|
|
||||||
7. Scale up statefulsets to 4 replicas.
|
|
||||||
8. Scale down statefulsets to 0 replicas and delete all pods.
|
|
||||||
9. Delete all PVCs from the test namespace.
|
|
||||||
10. Delete the storage class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
|
||||||
manifestPath = "test/e2e/testing-manifests/statefulset/nginx"
|
|
||||||
mountPath = "/usr/share/nginx/html"
|
|
||||||
storageclassname = "nginx-sc"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("vsphere statefulset", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("vsphere-statefulset")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
namespace string
|
|
||||||
client clientset.Interface
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func() {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
client = f.ClientSet
|
|
||||||
Bootstrap(f)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("vsphere statefulset testing", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Creating StorageClass for Statefulset")
|
|
||||||
scParameters := make(map[string]string)
|
|
||||||
scParameters["diskformat"] = "thin"
|
|
||||||
scSpec := getVSphereStorageClassSpec(storageclassname, scParameters, nil, "")
|
|
||||||
sc, err := client.StorageV1().StorageClasses().Create(ctx, scSpec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), sc.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating statefulset")
|
|
||||||
|
|
||||||
statefulset := e2estatefulset.CreateStatefulSet(ctx, client, manifestPath, namespace)
|
|
||||||
ginkgo.DeferCleanup(e2estatefulset.DeleteAllStatefulSets, client, namespace)
|
|
||||||
replicas := *(statefulset.Spec.Replicas)
|
|
||||||
// Waiting for pods status to be Ready
|
|
||||||
e2estatefulset.WaitForStatusReadyReplicas(ctx, client, statefulset, replicas)
|
|
||||||
framework.ExpectNoError(e2estatefulset.CheckMount(ctx, client, statefulset, mountPath))
|
|
||||||
ssPodsBeforeScaleDown := e2estatefulset.GetPodList(ctx, client, statefulset)
|
|
||||||
gomega.Expect(ssPodsBeforeScaleDown.Items).NotTo(gomega.BeEmpty(), fmt.Sprintf("Unable to get list of Pods from the Statefulset: %v", statefulset.Name))
|
|
||||||
gomega.Expect(ssPodsBeforeScaleDown.Items).To(gomega.HaveLen(int(replicas)), "Number of Pods in the statefulset should match with number of replicas")
|
|
||||||
|
|
||||||
// Get the list of Volumes attached to Pods before scale down
|
|
||||||
volumesBeforeScaleDown := make(map[string]string)
|
|
||||||
for _, sspod := range ssPodsBeforeScaleDown.Items {
|
|
||||||
_, err := client.CoreV1().Pods(namespace).Get(ctx, sspod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
for _, volumespec := range sspod.Spec.Volumes {
|
|
||||||
if volumespec.PersistentVolumeClaim != nil {
|
|
||||||
volumePath := getvSphereVolumePathFromClaim(ctx, client, statefulset.Namespace, volumespec.PersistentVolumeClaim.ClaimName)
|
|
||||||
volumesBeforeScaleDown[volumePath] = volumespec.PersistentVolumeClaim.ClaimName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Scaling down statefulsets to number of Replica: %v", replicas-1))
|
|
||||||
_, scaledownErr := e2estatefulset.Scale(ctx, client, statefulset, replicas-1)
|
|
||||||
framework.ExpectNoError(scaledownErr)
|
|
||||||
e2estatefulset.WaitForStatusReadyReplicas(ctx, client, statefulset, replicas-1)
|
|
||||||
|
|
||||||
// After scale down, verify vsphere volumes are detached from deleted pods
|
|
||||||
ginkgo.By("Verify Volumes are detached from Nodes after Statefulsets is scaled down")
|
|
||||||
for _, sspod := range ssPodsBeforeScaleDown.Items {
|
|
||||||
_, err := client.CoreV1().Pods(namespace).Get(ctx, sspod.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
if !apierrors.IsNotFound(err) {
|
|
||||||
framework.Failf("Error in getting Pod %s: %v", sspod.Name, err)
|
|
||||||
}
|
|
||||||
for _, volumespec := range sspod.Spec.Volumes {
|
|
||||||
if volumespec.PersistentVolumeClaim != nil {
|
|
||||||
vSpherediskPath := getvSphereVolumePathFromClaim(ctx, client, statefulset.Namespace, volumespec.PersistentVolumeClaim.ClaimName)
|
|
||||||
framework.Logf("Waiting for Volume: %q to detach from Node: %q", vSpherediskPath, sspod.Spec.NodeName)
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, vSpherediskPath, sspod.Spec.NodeName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Scaling up statefulsets to number of Replica: %v", replicas))
|
|
||||||
_, scaleupErr := e2estatefulset.Scale(ctx, client, statefulset, replicas)
|
|
||||||
framework.ExpectNoError(scaleupErr)
|
|
||||||
e2estatefulset.WaitForStatusReplicas(ctx, client, statefulset, replicas)
|
|
||||||
e2estatefulset.WaitForStatusReadyReplicas(ctx, client, statefulset, replicas)
|
|
||||||
|
|
||||||
ssPodsAfterScaleUp := e2estatefulset.GetPodList(ctx, client, statefulset)
|
|
||||||
gomega.Expect(ssPodsAfterScaleUp.Items).NotTo(gomega.BeEmpty(), fmt.Sprintf("Unable to get list of Pods from the Statefulset: %v", statefulset.Name))
|
|
||||||
gomega.Expect(ssPodsAfterScaleUp.Items).To(gomega.HaveLen(int(replicas)), "Number of Pods in the statefulset should match with number of replicas")
|
|
||||||
|
|
||||||
// After scale up, verify all vsphere volumes are attached to node VMs.
|
|
||||||
ginkgo.By("Verify all volumes are attached to Nodes after Statefulsets is scaled up")
|
|
||||||
for _, sspod := range ssPodsAfterScaleUp.Items {
|
|
||||||
err := e2epod.WaitTimeoutForPodReadyInNamespace(ctx, client, sspod.Name, statefulset.Namespace, framework.PodStartTimeout)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
pod, err := client.CoreV1().Pods(namespace).Get(ctx, sspod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
for _, volumespec := range pod.Spec.Volumes {
|
|
||||||
if volumespec.PersistentVolumeClaim != nil {
|
|
||||||
vSpherediskPath := getvSphereVolumePathFromClaim(ctx, client, statefulset.Namespace, volumespec.PersistentVolumeClaim.ClaimName)
|
|
||||||
framework.Logf("Verify Volume: %q is attached to the Node: %q", vSpherediskPath, sspod.Spec.NodeName)
|
|
||||||
// Verify scale up has re-attached the same volumes and not introduced new volume
|
|
||||||
if volumesBeforeScaleDown[vSpherediskPath] == "" {
|
|
||||||
framework.Failf("Volume: %q was not attached to the Node: %q before scale down", vSpherediskPath, sspod.Spec.NodeName)
|
|
||||||
}
|
|
||||||
isVolumeAttached, verifyDiskAttachedError := diskIsAttached(ctx, vSpherediskPath, sspod.Spec.NodeName)
|
|
||||||
if !isVolumeAttached {
|
|
||||||
framework.Failf("Volume: %q is not attached to the Node: %q", vSpherediskPath, sspod.Spec.NodeName)
|
|
||||||
}
|
|
||||||
framework.ExpectNoError(verifyDiskAttachedError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Induce stress to create volumes in parallel with multiple threads based on user configurable values for number of threads and iterations per thread.
|
|
||||||
The following actions will be performed as part of this test.
|
|
||||||
|
|
||||||
1. Create Storage Classes of 4 Categories (Default, SC with Non Default Datastore, SC with SPBM Policy, SC with VSAN Storage Capabilities.)
|
|
||||||
2. READ VCP_STRESS_INSTANCES, VCP_STRESS_ITERATIONS, VSPHERE_SPBM_POLICY_NAME and VSPHERE_DATASTORE from System Environment.
|
|
||||||
3. Launch goroutine for volume lifecycle operations.
|
|
||||||
4. Each instance of routine iterates for n times, where n is read from system env - VCP_STRESS_ITERATIONS
|
|
||||||
5. Each iteration creates 1 PVC, 1 POD using the provisioned PV, Verify disk is attached to the node, Verify pod can access the volume, delete the pod and finally delete the PVC.
|
|
||||||
*/
|
|
||||||
var _ = utils.SIGDescribe("vsphere cloud provider stress", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("vcp-stress")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
instances int
|
|
||||||
iterations int
|
|
||||||
policyName string
|
|
||||||
datastoreName string
|
|
||||||
scNames = []string{storageclass1, storageclass2, storageclass3, storageclass4}
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
|
|
||||||
nodeList, err := e2enode.GetReadySchedulableNodes(ctx, f.ClientSet)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// if VCP_STRESS_INSTANCES = 12 and VCP_STRESS_ITERATIONS is 10. 12 threads will run in parallel for 10 times.
|
|
||||||
// Resulting 120 Volumes and POD Creation. Volumes will be provisioned with each different types of Storage Class,
|
|
||||||
// Each iteration creates PVC, verify PV is provisioned, then creates a pod, verify volume is attached to the node, and then delete the pod and delete pvc.
|
|
||||||
instances = GetAndExpectIntEnvVar(VCPStressInstances)
|
|
||||||
if instances > volumesPerNode*len(nodeList.Items) {
|
|
||||||
framework.Failf("Number of Instances should be less or equal: %v, got instead %v", volumesPerNode*len(nodeList.Items), instances)
|
|
||||||
}
|
|
||||||
if instances <= len(scNames) {
|
|
||||||
framework.Failf("VCP_STRESS_INSTANCES should be greater than 3 to utilize all 4 types of storage classes, got instead %v", instances)
|
|
||||||
}
|
|
||||||
|
|
||||||
iterations = GetAndExpectIntEnvVar(VCPStressIterations)
|
|
||||||
if iterations <= 0 {
|
|
||||||
framework.Failf("VCP_STRESS_ITERATIONS should be greater than 0, got instead %v", iterations)
|
|
||||||
}
|
|
||||||
|
|
||||||
policyName = GetAndExpectStringEnvVar(SPBMPolicyName)
|
|
||||||
datastoreName = GetAndExpectStringEnvVar(StorageClassDatastoreName)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("vsphere stress tests", func(ctx context.Context) {
|
|
||||||
scArrays := make([]*storagev1.StorageClass, len(scNames))
|
|
||||||
for index, scname := range scNames {
|
|
||||||
// Create vSphere Storage Class
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating Storage Class : %v", scname))
|
|
||||||
var sc *storagev1.StorageClass
|
|
||||||
var err error
|
|
||||||
switch scname {
|
|
||||||
case storageclass1:
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass1, nil, nil, ""), metav1.CreateOptions{})
|
|
||||||
case storageclass2:
|
|
||||||
var scVSanParameters map[string]string
|
|
||||||
scVSanParameters = make(map[string]string)
|
|
||||||
scVSanParameters[PolicyHostFailuresToTolerate] = "1"
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass2, scVSanParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
case storageclass3:
|
|
||||||
var scSPBMPolicyParameters map[string]string
|
|
||||||
scSPBMPolicyParameters = make(map[string]string)
|
|
||||||
scSPBMPolicyParameters[SpbmStoragePolicy] = policyName
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass3, scSPBMPolicyParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
case storageclass4:
|
|
||||||
var scWithDSParameters map[string]string
|
|
||||||
scWithDSParameters = make(map[string]string)
|
|
||||||
scWithDSParameters[Datastore] = datastoreName
|
|
||||||
scWithDatastoreSpec := getVSphereStorageClassSpec(storageclass4, scWithDSParameters, nil, "")
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, scWithDatastoreSpec, metav1.CreateOptions{})
|
|
||||||
}
|
|
||||||
gomega.Expect(sc).NotTo(gomega.BeNil())
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), scname, metav1.DeleteOptions{})
|
|
||||||
scArrays[index] = sc
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(instances)
|
|
||||||
for instanceCount := 0; instanceCount < instances; instanceCount++ {
|
|
||||||
instanceID := fmt.Sprintf("Thread:%v", instanceCount+1)
|
|
||||||
go PerformVolumeLifeCycleInParallel(ctx, f, client, namespace, instanceID, scArrays[instanceCount%len(scArrays)], iterations, &wg)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// PerformVolumeLifeCycleInParallel performs volume lifecycle operations
|
|
||||||
// Called as a go routine to perform operations in parallel
|
|
||||||
func PerformVolumeLifeCycleInParallel(ctx context.Context, f *framework.Framework, client clientset.Interface, namespace string, instanceID string, sc *storagev1.StorageClass, iterations int, wg *sync.WaitGroup) {
|
|
||||||
defer wg.Done()
|
|
||||||
defer ginkgo.GinkgoRecover()
|
|
||||||
|
|
||||||
for iterationCount := 0; iterationCount < iterations; iterationCount++ {
|
|
||||||
logPrefix := fmt.Sprintf("Instance: [%v], Iteration: [%v] :", instanceID, iterationCount+1)
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Creating PVC using the Storage Class: %v", logPrefix, sc.Name))
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "1Gi", sc))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Waiting for claim: %v to be in bound phase", logPrefix, pvclaim.Name))
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Creating Pod using the claim: %v", logPrefix, pvclaim.Name))
|
|
||||||
// Create pod to attach Volume to Node
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, nil, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Waiting for the Pod: %v to be in the running state", logPrefix, pod.Name))
|
|
||||||
err = e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name, f.Timeouts.PodStartSlow)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Get the copy of the Pod to know the assigned node name.
|
|
||||||
pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Verifying the volume: %v is attached to the node VM: %v", logPrefix, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName))
|
|
||||||
isVolumeAttached, verifyDiskAttachedError := diskIsAttached(ctx, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName)
|
|
||||||
if !isVolumeAttached {
|
|
||||||
framework.Failf("Volume: %s is not attached to the node: %v", persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName)
|
|
||||||
}
|
|
||||||
framework.ExpectNoError(verifyDiskAttachedError)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Verifying the volume: %v is accessible in the pod: %v", logPrefix, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Name))
|
|
||||||
verifyVSphereVolumesAccessible(ctx, client, pod, persistentvolumes)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Deleting pod: %v", logPrefix, pod.Name))
|
|
||||||
err = e2epod.DeletePodWithWait(ctx, client, pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Waiting for volume: %v to be detached from the node: %v", logPrefix, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName))
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("%v Deleting the Claim: %v", logPrefix, pvclaim.Name))
|
|
||||||
err = e2epv.DeletePersistentVolumeClaim(ctx, client, pvclaim.Name, namespace)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,848 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
"github.com/vmware/govmomi/find"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
|
||||||
vim25types "github.com/vmware/govmomi/vim25/types"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/rand"
|
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
volumesPerNode = 55
|
|
||||||
storageclass1 = "sc-default"
|
|
||||||
storageclass2 = "sc-vsan"
|
|
||||||
storageclass3 = "sc-spbm"
|
|
||||||
storageclass4 = "sc-user-specified-ds"
|
|
||||||
dummyDiskName = "kube-dummyDisk.vmdk"
|
|
||||||
providerPrefix = "vsphere://"
|
|
||||||
)
|
|
||||||
|
|
||||||
// volumeState represents the state of a volume.
|
|
||||||
type volumeState int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
volumeStateDetached volumeState = 1
|
|
||||||
volumeStateAttached volumeState = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wait until vsphere volumes are detached from the list of nodes or time out after 5 minutes
|
|
||||||
func waitForVSphereDisksToDetach(ctx context.Context, nodeVolumes map[string][]string) error {
|
|
||||||
var (
|
|
||||||
detachTimeout = 5 * time.Minute
|
|
||||||
detachPollTime = 10 * time.Second
|
|
||||||
)
|
|
||||||
waitErr := wait.PollWithContext(ctx, detachPollTime, detachTimeout, func(ctx context.Context) (bool, error) {
|
|
||||||
attachedResult, err := disksAreAttached(ctx, nodeVolumes)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
for nodeName, nodeVolumes := range attachedResult {
|
|
||||||
for volumePath, attached := range nodeVolumes {
|
|
||||||
if attached {
|
|
||||||
framework.Logf("Volume %q is still attached to %q.", volumePath, string(nodeName))
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
framework.Logf("Volume are successfully detached from all the nodes: %+v", nodeVolumes)
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
if waitErr != nil {
|
|
||||||
if wait.Interrupted(waitErr) {
|
|
||||||
return fmt.Errorf("volumes have not detached after %v: %v", detachTimeout, waitErr)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("error waiting for volumes to detach: %v", waitErr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until vsphere vmdk moves to expected state on the given node, or time out after 6 minutes
|
|
||||||
func waitForVSphereDiskStatus(ctx context.Context, volumePath string, nodeName string, expectedState volumeState) error {
|
|
||||||
var (
|
|
||||||
currentState volumeState
|
|
||||||
timeout = 6 * time.Minute
|
|
||||||
pollTime = 10 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
var attachedState = map[bool]volumeState{
|
|
||||||
true: volumeStateAttached,
|
|
||||||
false: volumeStateDetached,
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachedStateMsg = map[volumeState]string{
|
|
||||||
volumeStateAttached: "attached to",
|
|
||||||
volumeStateDetached: "detached from",
|
|
||||||
}
|
|
||||||
|
|
||||||
waitErr := wait.PollWithContext(ctx, pollTime, timeout, func(ctx context.Context) (bool, error) {
|
|
||||||
diskAttached, err := diskIsAttached(ctx, volumePath, nodeName)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState = attachedState[diskAttached]
|
|
||||||
if currentState == expectedState {
|
|
||||||
framework.Logf("Volume %q has successfully %s %q", volumePath, attachedStateMsg[currentState], nodeName)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
framework.Logf("Waiting for Volume %q to be %s %q.", volumePath, attachedStateMsg[expectedState], nodeName)
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
if waitErr != nil {
|
|
||||||
if wait.Interrupted(waitErr) {
|
|
||||||
return fmt.Errorf("volume %q is not %s %q after %v: %v", volumePath, attachedStateMsg[expectedState], nodeName, timeout, waitErr)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("error waiting for volume %q to be %s %q: %v", volumePath, attachedStateMsg[expectedState], nodeName, waitErr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until vsphere vmdk is attached from the given node or time out after 6 minutes
|
|
||||||
func waitForVSphereDiskToAttach(ctx context.Context, volumePath string, nodeName string) error {
|
|
||||||
return waitForVSphereDiskStatus(ctx, volumePath, nodeName, volumeStateAttached)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until vsphere vmdk is detached from the given node or time out after 6 minutes
|
|
||||||
func waitForVSphereDiskToDetach(ctx context.Context, volumePath string, nodeName string) error {
|
|
||||||
return waitForVSphereDiskStatus(ctx, volumePath, nodeName, volumeStateDetached)
|
|
||||||
}
|
|
||||||
|
|
||||||
// function to create vsphere volume spec with given VMDK volume path, Reclaim Policy and labels
|
|
||||||
func getVSpherePersistentVolumeSpec(volumePath string, persistentVolumeReclaimPolicy v1.PersistentVolumeReclaimPolicy, labels map[string]string) *v1.PersistentVolume {
|
|
||||||
return e2epv.MakePersistentVolume(e2epv.PersistentVolumeConfig{
|
|
||||||
NamePrefix: "vspherepv-",
|
|
||||||
PVSource: v1.PersistentVolumeSource{
|
|
||||||
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
|
|
||||||
VolumePath: volumePath,
|
|
||||||
FSType: "ext4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ReclaimPolicy: persistentVolumeReclaimPolicy,
|
|
||||||
Capacity: "2Gi",
|
|
||||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
|
||||||
v1.ReadWriteOnce,
|
|
||||||
},
|
|
||||||
Labels: labels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// function to get vsphere persistent volume spec with given selector labels.
|
|
||||||
func getVSpherePersistentVolumeClaimSpec(namespace string, labels map[string]string) *v1.PersistentVolumeClaim {
|
|
||||||
var (
|
|
||||||
pvc *v1.PersistentVolumeClaim
|
|
||||||
)
|
|
||||||
pvc = &v1.PersistentVolumeClaim{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
GenerateName: "pvc-",
|
|
||||||
Namespace: namespace,
|
|
||||||
},
|
|
||||||
Spec: v1.PersistentVolumeClaimSpec{
|
|
||||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
|
||||||
v1.ReadWriteOnce,
|
|
||||||
},
|
|
||||||
Resources: v1.VolumeResourceRequirements{
|
|
||||||
Requests: v1.ResourceList{
|
|
||||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse("2Gi"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if labels != nil {
|
|
||||||
pvc.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pvc
|
|
||||||
}
|
|
||||||
|
|
||||||
// function to write content to the volume backed by given PVC
|
|
||||||
func writeContentToVSpherePV(ctx context.Context, client clientset.Interface, timeouts *framework.TimeoutContext, pvc *v1.PersistentVolumeClaim, expectedContent string) {
|
|
||||||
utils.RunInPodWithVolume(ctx, client, timeouts, pvc.Namespace, pvc.Name, "echo "+expectedContent+" > /mnt/test/data")
|
|
||||||
framework.Logf("Done with writing content to volume")
|
|
||||||
}
|
|
||||||
|
|
||||||
// function to verify content is matching on the volume backed for given PVC
|
|
||||||
func verifyContentOfVSpherePV(ctx context.Context, client clientset.Interface, timeouts *framework.TimeoutContext, pvc *v1.PersistentVolumeClaim, expectedContent string) {
|
|
||||||
utils.RunInPodWithVolume(ctx, client, timeouts, pvc.Namespace, pvc.Name, "grep '"+expectedContent+"' /mnt/test/data")
|
|
||||||
framework.Logf("Successfully verified content of the volume")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVSphereStorageClassSpec(name string, scParameters map[string]string, zones []string, volumeBindingMode storagev1.VolumeBindingMode) *storagev1.StorageClass {
|
|
||||||
var sc *storagev1.StorageClass
|
|
||||||
|
|
||||||
sc = &storagev1.StorageClass{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "StorageClass",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
Provisioner: "kubernetes.io/vsphere-volume",
|
|
||||||
}
|
|
||||||
if scParameters != nil {
|
|
||||||
sc.Parameters = scParameters
|
|
||||||
}
|
|
||||||
if zones != nil {
|
|
||||||
term := v1.TopologySelectorTerm{
|
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
|
||||||
{
|
|
||||||
Key: v1.LabelTopologyZone,
|
|
||||||
Values: zones,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
sc.AllowedTopologies = append(sc.AllowedTopologies, term)
|
|
||||||
}
|
|
||||||
if volumeBindingMode != "" {
|
|
||||||
mode := storagev1.VolumeBindingMode(string(volumeBindingMode))
|
|
||||||
sc.VolumeBindingMode = &mode
|
|
||||||
}
|
|
||||||
return sc
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVSphereClaimSpecWithStorageClass(ns string, diskSize string, storageclass *storagev1.StorageClass) *v1.PersistentVolumeClaim {
|
|
||||||
claim := &v1.PersistentVolumeClaim{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
GenerateName: "pvc-",
|
|
||||||
Namespace: ns,
|
|
||||||
},
|
|
||||||
Spec: v1.PersistentVolumeClaimSpec{
|
|
||||||
AccessModes: []v1.PersistentVolumeAccessMode{
|
|
||||||
v1.ReadWriteOnce,
|
|
||||||
},
|
|
||||||
Resources: v1.VolumeResourceRequirements{
|
|
||||||
Requests: v1.ResourceList{
|
|
||||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse(diskSize),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
StorageClassName: &(storageclass.Name),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return claim
|
|
||||||
}
|
|
||||||
|
|
||||||
// func to get pod spec with given volume claim, node selector labels and command
|
|
||||||
func getVSpherePodSpecWithClaim(claimName string, nodeSelectorKV map[string]string, command string) *v1.Pod {
|
|
||||||
pod := &v1.Pod{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Pod",
|
|
||||||
APIVersion: "v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
GenerateName: "pod-pvc-",
|
|
||||||
},
|
|
||||||
Spec: v1.PodSpec{
|
|
||||||
Containers: []v1.Container{
|
|
||||||
{
|
|
||||||
Name: "volume-tester",
|
|
||||||
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
|
||||||
Command: []string{"/bin/sh"},
|
|
||||||
Args: []string{"-c", command},
|
|
||||||
VolumeMounts: []v1.VolumeMount{
|
|
||||||
{
|
|
||||||
Name: "my-volume",
|
|
||||||
MountPath: "/mnt/test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RestartPolicy: v1.RestartPolicyNever,
|
|
||||||
Volumes: []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: "my-volume",
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
|
||||||
ClaimName: claimName,
|
|
||||||
ReadOnly: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if nodeSelectorKV != nil {
|
|
||||||
pod.Spec.NodeSelector = nodeSelectorKV
|
|
||||||
}
|
|
||||||
return pod
|
|
||||||
}
|
|
||||||
|
|
||||||
// func to get pod spec with given volume paths, node selector labels and container commands
|
|
||||||
func getVSpherePodSpecWithVolumePaths(volumePaths []string, keyValuelabel map[string]string, commands []string) *v1.Pod {
|
|
||||||
var volumeMounts []v1.VolumeMount
|
|
||||||
var volumes []v1.Volume
|
|
||||||
|
|
||||||
for index, volumePath := range volumePaths {
|
|
||||||
name := fmt.Sprintf("volume%v", index+1)
|
|
||||||
volumeMounts = append(volumeMounts, v1.VolumeMount{Name: name, MountPath: "/mnt/" + name})
|
|
||||||
vsphereVolume := new(v1.VsphereVirtualDiskVolumeSource)
|
|
||||||
vsphereVolume.VolumePath = volumePath
|
|
||||||
vsphereVolume.FSType = "ext4"
|
|
||||||
volumes = append(volumes, v1.Volume{Name: name})
|
|
||||||
volumes[index].VolumeSource.VsphereVolume = vsphereVolume
|
|
||||||
}
|
|
||||||
|
|
||||||
if commands == nil || len(commands) == 0 {
|
|
||||||
commands = []string{
|
|
||||||
"/bin/sh",
|
|
||||||
"-c",
|
|
||||||
"while true; do sleep 2; done",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pod := &v1.Pod{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Pod",
|
|
||||||
APIVersion: "v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
GenerateName: "vsphere-e2e-",
|
|
||||||
},
|
|
||||||
Spec: v1.PodSpec{
|
|
||||||
Containers: []v1.Container{
|
|
||||||
{
|
|
||||||
Name: "vsphere-e2e-container-" + string(uuid.NewUUID()),
|
|
||||||
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
|
||||||
Command: commands,
|
|
||||||
VolumeMounts: volumeMounts,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RestartPolicy: v1.RestartPolicyNever,
|
|
||||||
Volumes: volumes,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyValuelabel != nil {
|
|
||||||
pod.Spec.NodeSelector = keyValuelabel
|
|
||||||
}
|
|
||||||
return pod
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyFilesExistOnVSphereVolume(namespace string, podName string, filePaths ...string) {
|
|
||||||
for _, filePath := range filePaths {
|
|
||||||
_, err := e2ekubectl.RunKubectl(namespace, "exec", podName, "--", "/bin/ls", filePath)
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("failed to verify file: %q on the pod: %q", filePath, podName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createEmptyFilesOnVSphereVolume(namespace string, podName string, filePaths []string) {
|
|
||||||
for _, filePath := range filePaths {
|
|
||||||
err := e2eoutput.CreateEmptyFileOnPod(namespace, podName, filePath)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify volumes are attached to the node and are accessible in pod
|
|
||||||
func verifyVSphereVolumesAccessible(ctx context.Context, c clientset.Interface, pod *v1.Pod, persistentvolumes []*v1.PersistentVolume) {
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
namespace := pod.Namespace
|
|
||||||
for index, pv := range persistentvolumes {
|
|
||||||
// Verify disks are attached to the node
|
|
||||||
isAttached, err := diskIsAttached(ctx, pv.Spec.VsphereVolume.VolumePath, nodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if !isAttached {
|
|
||||||
framework.Failf("disk %v is not attached to the node: %v", pv.Spec.VsphereVolume.VolumePath, nodeName)
|
|
||||||
}
|
|
||||||
// Verify Volumes are accessible
|
|
||||||
filepath := filepath.Join("/mnt/", fmt.Sprintf("volume%v", index+1), "/emptyFile.txt")
|
|
||||||
_, err = e2eoutput.LookForStringInPodExec(namespace, pod.Name, []string{"/bin/touch", filepath}, "", time.Minute)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify volumes are created on one of the specified zones
|
|
||||||
func verifyVolumeCreationOnRightZone(ctx context.Context, persistentvolumes []*v1.PersistentVolume, nodeName string, zones []string) {
|
|
||||||
for _, pv := range persistentvolumes {
|
|
||||||
volumePath := pv.Spec.VsphereVolume.VolumePath
|
|
||||||
// Extract datastoreName from the volume path in the pv spec
|
|
||||||
// For example : "vsanDatastore" is extracted from "[vsanDatastore] 25d8b159-948c-4b73-e499-02001ad1b044/volume.vmdk"
|
|
||||||
datastorePathObj, _ := getDatastorePathObjFromVMDiskPath(volumePath)
|
|
||||||
datastoreName := datastorePathObj.Datastore
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodeName)
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
// Get the datastore object reference from the datastore name
|
|
||||||
datastoreRef, err := nodeInfo.VSphere.GetDatastoreRefFromName(ctx, nodeInfo.DataCenterRef, datastoreName)
|
|
||||||
if err != nil {
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
// Find common datastores among the specified zones
|
|
||||||
var datastoreCountMap = make(map[string]int)
|
|
||||||
numZones := len(zones)
|
|
||||||
var commonDatastores []string
|
|
||||||
for _, zone := range zones {
|
|
||||||
datastoreInZone := TestContext.NodeMapper.GetDatastoresInZone(nodeInfo.VSphere.Config.Hostname, zone)
|
|
||||||
for _, datastore := range datastoreInZone {
|
|
||||||
datastoreCountMap[datastore] = datastoreCountMap[datastore] + 1
|
|
||||||
if datastoreCountMap[datastore] == numZones {
|
|
||||||
commonDatastores = append(commonDatastores, datastore)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gomega.Expect(commonDatastores).To(gomega.ContainElement(datastoreRef.Value), "PV was created in an unsupported zone.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get vSphere Volume Path from PVC
|
|
||||||
func getvSphereVolumePathFromClaim(ctx context.Context, client clientset.Interface, namespace string, claimName string) string {
|
|
||||||
pvclaim, err := client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, claimName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
pv, err := client.CoreV1().PersistentVolumes().Get(ctx, pvclaim.Spec.VolumeName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
return pv.Spec.VsphereVolume.VolumePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get canonical volume path for volume Path.
|
|
||||||
// Example1: The canonical path for volume path - [vsanDatastore] kubevols/volume.vmdk will be [vsanDatastore] 25d8b159-948c-4b73-e499-02001ad1b044/volume.vmdk
|
|
||||||
// Example2: The canonical path for volume path - [vsanDatastore] 25d8b159-948c-4b73-e499-02001ad1b044/volume.vmdk will be same as volume Path.
|
|
||||||
func getCanonicalVolumePath(ctx context.Context, dc *object.Datacenter, volumePath string) (string, error) {
|
|
||||||
var folderID string
|
|
||||||
canonicalVolumePath := volumePath
|
|
||||||
dsPathObj, err := getDatastorePathObjFromVMDiskPath(volumePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
dsPath := strings.Split(strings.TrimSpace(dsPathObj.Path), "/")
|
|
||||||
if len(dsPath) <= 1 {
|
|
||||||
return canonicalVolumePath, nil
|
|
||||||
}
|
|
||||||
datastore := dsPathObj.Datastore
|
|
||||||
dsFolder := dsPath[0]
|
|
||||||
// Get the datastore folder ID if datastore or folder doesn't exist in datastoreFolderIDMap
|
|
||||||
if !isValidUUID(dsFolder) {
|
|
||||||
dummyDiskVolPath := "[" + datastore + "] " + dsFolder + "/" + dummyDiskName
|
|
||||||
// Querying a non-existent dummy disk on the datastore folder.
|
|
||||||
// It would fail and return an folder ID in the error message.
|
|
||||||
_, err := getVirtualDiskPage83Data(ctx, dc, dummyDiskVolPath)
|
|
||||||
if err != nil {
|
|
||||||
re := regexp.MustCompile("File (.*?) was not found")
|
|
||||||
match := re.FindStringSubmatch(err.Error())
|
|
||||||
canonicalVolumePath = match[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diskPath := getPathFromVMDiskPath(canonicalVolumePath)
|
|
||||||
if diskPath == "" {
|
|
||||||
return "", fmt.Errorf("Failed to parse canonicalVolumePath: %s in getcanonicalVolumePath method", canonicalVolumePath)
|
|
||||||
}
|
|
||||||
folderID = strings.Split(strings.TrimSpace(diskPath), "/")[0]
|
|
||||||
canonicalVolumePath = strings.Replace(volumePath, dsFolder, folderID, 1)
|
|
||||||
return canonicalVolumePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPathFromVMDiskPath retrieves the path from VM Disk Path.
|
|
||||||
// Example: For vmDiskPath - [vsanDatastore] kubevols/volume.vmdk, the path is kubevols/volume.vmdk
|
|
||||||
func getPathFromVMDiskPath(vmDiskPath string) string {
|
|
||||||
datastorePathObj := new(object.DatastorePath)
|
|
||||||
isSuccess := datastorePathObj.FromString(vmDiskPath)
|
|
||||||
if !isSuccess {
|
|
||||||
framework.Logf("Failed to parse vmDiskPath: %s", vmDiskPath)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return datastorePathObj.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDatastorePathObjFromVMDiskPath gets the datastorePathObj from VM disk path.
|
|
||||||
func getDatastorePathObjFromVMDiskPath(vmDiskPath string) (*object.DatastorePath, error) {
|
|
||||||
datastorePathObj := new(object.DatastorePath)
|
|
||||||
isSuccess := datastorePathObj.FromString(vmDiskPath)
|
|
||||||
if !isSuccess {
|
|
||||||
framework.Logf("Failed to parse volPath: %s", vmDiskPath)
|
|
||||||
return nil, fmt.Errorf("Failed to parse volPath: %s", vmDiskPath)
|
|
||||||
}
|
|
||||||
return datastorePathObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getVirtualDiskPage83Data gets the virtual disk UUID by diskPath
|
|
||||||
func getVirtualDiskPage83Data(ctx context.Context, dc *object.Datacenter, diskPath string) (string, error) {
|
|
||||||
if len(diskPath) > 0 && filepath.Ext(diskPath) != ".vmdk" {
|
|
||||||
diskPath += ".vmdk"
|
|
||||||
}
|
|
||||||
vdm := object.NewVirtualDiskManager(dc.Client())
|
|
||||||
// Returns uuid of vmdk virtual disk
|
|
||||||
diskUUID, err := vdm.QueryVirtualDiskUuid(ctx, diskPath, dc)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
klog.Warningf("QueryVirtualDiskUuid failed for diskPath: %q. err: %+v", diskPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
diskUUID = formatVirtualDiskUUID(diskUUID)
|
|
||||||
return diskUUID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatVirtualDiskUUID removes any spaces and hyphens in UUID
|
|
||||||
// Example UUID input is 42375390-71f9-43a3-a770-56803bcd7baa and output after format is 4237539071f943a3a77056803bcd7baa
|
|
||||||
func formatVirtualDiskUUID(uuid string) string {
|
|
||||||
uuidwithNoSpace := strings.Replace(uuid, " ", "", -1)
|
|
||||||
uuidWithNoHypens := strings.Replace(uuidwithNoSpace, "-", "", -1)
|
|
||||||
return strings.ToLower(uuidWithNoHypens)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValidUUID checks if the string is a valid UUID.
|
|
||||||
func isValidUUID(uuid string) bool {
|
|
||||||
r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$")
|
|
||||||
return r.MatchString(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeStorageClusterORFolderNameFromVDiskPath removes the cluster or folder path from the vDiskPath
|
|
||||||
// for vDiskPath [DatastoreCluster/sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk, return value is [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk
|
|
||||||
// for vDiskPath [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk, return value remains same [sharedVmfs-0] kubevols/e2e-vmdk-1234.vmdk
|
|
||||||
func removeStorageClusterORFolderNameFromVDiskPath(vDiskPath string) string {
|
|
||||||
datastore := regexp.MustCompile("\\[(.*?)\\]").FindStringSubmatch(vDiskPath)[1]
|
|
||||||
if filepath.Base(datastore) != datastore {
|
|
||||||
vDiskPath = strings.Replace(vDiskPath, datastore, filepath.Base(datastore), 1)
|
|
||||||
}
|
|
||||||
return vDiskPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// getVirtualDeviceByPath gets the virtual device by path
|
|
||||||
func getVirtualDeviceByPath(ctx context.Context, vm *object.VirtualMachine, diskPath string) (vim25types.BaseVirtualDevice, error) {
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to get the devices for VM: %q. err: %+v", vm.InventoryPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter vm devices to retrieve device for the given vmdk file identified by disk path
|
|
||||||
for _, device := range vmDevices {
|
|
||||||
if vmDevices.TypeName(device) == "VirtualDisk" {
|
|
||||||
virtualDevice := device.GetVirtualDevice()
|
|
||||||
if backing, ok := virtualDevice.Backing.(*vim25types.VirtualDiskFlatVer2BackingInfo); ok {
|
|
||||||
if matchVirtualDiskAndVolPath(backing.FileName, diskPath) {
|
|
||||||
framework.Logf("Found VirtualDisk backing with filename %q for diskPath %q", backing.FileName, diskPath)
|
|
||||||
return device, nil
|
|
||||||
}
|
|
||||||
framework.Logf("VirtualDisk backing filename %q does not match with diskPath %q", backing.FileName, diskPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchVirtualDiskAndVolPath(diskPath, volPath string) bool {
|
|
||||||
fileExt := ".vmdk"
|
|
||||||
diskPath = strings.TrimSuffix(diskPath, fileExt)
|
|
||||||
volPath = strings.TrimSuffix(volPath, fileExt)
|
|
||||||
return diskPath == volPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertVolPathsToDevicePaths removes cluster or folder path from volPaths and convert to canonicalPath
|
|
||||||
func convertVolPathsToDevicePaths(ctx context.Context, nodeVolumes map[string][]string) (map[string][]string, error) {
|
|
||||||
vmVolumes := make(map[string][]string)
|
|
||||||
for nodeName, volPaths := range nodeVolumes {
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodeName)
|
|
||||||
datacenter := nodeInfo.VSphere.GetDatacenterFromObjectReference(ctx, nodeInfo.DataCenterRef)
|
|
||||||
for i, volPath := range volPaths {
|
|
||||||
deviceVolPath, err := convertVolPathToDevicePath(ctx, datacenter, volPath)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to convert vsphere volume path %s to device path for volume %s. err: %+v", volPath, deviceVolPath, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
volPaths[i] = deviceVolPath
|
|
||||||
}
|
|
||||||
vmVolumes[nodeName] = volPaths
|
|
||||||
}
|
|
||||||
return vmVolumes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertVolPathToDevicePath takes volPath and returns canonical volume path
|
|
||||||
func convertVolPathToDevicePath(ctx context.Context, dc *object.Datacenter, volPath string) (string, error) {
|
|
||||||
volPath = removeStorageClusterORFolderNameFromVDiskPath(volPath)
|
|
||||||
// Get the canonical volume path for volPath.
|
|
||||||
canonicalVolumePath, err := getCanonicalVolumePath(ctx, dc, volPath)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to get canonical vsphere volume path for volume: %s. err: %+v", volPath, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Check if the volume path contains .vmdk extension. If not, add the extension and update the nodeVolumes Map
|
|
||||||
if len(canonicalVolumePath) > 0 && filepath.Ext(canonicalVolumePath) != ".vmdk" {
|
|
||||||
canonicalVolumePath += ".vmdk"
|
|
||||||
}
|
|
||||||
return canonicalVolumePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get .vmx file path for a virtual machine
|
|
||||||
func getVMXFilePath(ctx context.Context, vmObject *object.VirtualMachine) (vmxPath string) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var nodeVM mo.VirtualMachine
|
|
||||||
err := vmObject.Properties(ctx, vmObject.Reference(), []string{"config.files"}, &nodeVM)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
gomega.Expect(nodeVM.Config).NotTo(gomega.BeNil())
|
|
||||||
|
|
||||||
vmxPath = nodeVM.Config.Files.VmPathName
|
|
||||||
framework.Logf("vmx file path is %s", vmxPath)
|
|
||||||
return vmxPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify ready node count. Try up to 3 minutes. Return true if count is expected count
|
|
||||||
func verifyReadyNodeCount(ctx context.Context, client clientset.Interface, expectedNodes int) bool {
|
|
||||||
numNodes := 0
|
|
||||||
for i := 0; i < 36; i++ {
|
|
||||||
nodeList, err := e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
numNodes = len(nodeList.Items)
|
|
||||||
if numNodes == expectedNodes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
|
||||||
return (numNodes == expectedNodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// poweroff nodeVM and confirm the poweroff state
|
|
||||||
func poweroffNodeVM(ctx context.Context, nodeName string, vm *object.VirtualMachine) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
framework.Logf("Powering off node VM %s", nodeName)
|
|
||||||
|
|
||||||
_, err := vm.PowerOff(ctx)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
err = vm.WaitForPowerState(ctx, vim25types.VirtualMachinePowerStatePoweredOff)
|
|
||||||
framework.ExpectNoError(err, "Unable to power off the node")
|
|
||||||
}
|
|
||||||
|
|
||||||
// poweron nodeVM and confirm the poweron state
|
|
||||||
func poweronNodeVM(ctx context.Context, nodeName string, vm *object.VirtualMachine) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
framework.Logf("Powering on node VM %s", nodeName)
|
|
||||||
|
|
||||||
vm.PowerOn(ctx)
|
|
||||||
err := vm.WaitForPowerState(ctx, vim25types.VirtualMachinePowerStatePoweredOn)
|
|
||||||
framework.ExpectNoError(err, "Unable to power on the node")
|
|
||||||
}
|
|
||||||
|
|
||||||
// unregister a nodeVM from VC
|
|
||||||
func unregisterNodeVM(ctx context.Context, nodeName string, vm *object.VirtualMachine) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
poweroffNodeVM(ctx, nodeName, vm)
|
|
||||||
|
|
||||||
framework.Logf("Unregistering node VM %s", nodeName)
|
|
||||||
err := vm.Unregister(ctx)
|
|
||||||
framework.ExpectNoError(err, "Unable to unregister the node")
|
|
||||||
}
|
|
||||||
|
|
||||||
// register a nodeVM into a VC
|
|
||||||
func registerNodeVM(ctx context.Context, nodeName, workingDir, vmxFilePath string, rpool *object.ResourcePool, host *object.HostSystem) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
framework.Logf("Registering node VM %s with vmx file path %s", nodeName, vmxFilePath)
|
|
||||||
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodeName)
|
|
||||||
finder := find.NewFinder(nodeInfo.VSphere.Client.Client, false)
|
|
||||||
|
|
||||||
vmFolder, err := finder.FolderOrDefault(ctx, workingDir)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
registerTask, err := vmFolder.RegisterVM(ctx, vmxFilePath, nodeName, false, rpool, host)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
err = registerTask.Wait(ctx)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
vmPath := filepath.Join(workingDir, nodeName)
|
|
||||||
vm, err := finder.VirtualMachine(ctx, vmPath)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
poweronNodeVM(ctx, nodeName, vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// disksAreAttached takes map of node and it's volumes and returns map of node, its volumes and attachment state
|
|
||||||
func disksAreAttached(ctx context.Context, nodeVolumes map[string][]string) (map[string]map[string]bool, error) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
disksAttached := make(map[string]map[string]bool)
|
|
||||||
if len(nodeVolumes) == 0 {
|
|
||||||
return disksAttached, nil
|
|
||||||
}
|
|
||||||
// Convert VolPaths into canonical form so that it can be compared with the VM device path.
|
|
||||||
vmVolumes, err := convertVolPathsToDevicePaths(ctx, nodeVolumes)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("Failed to convert volPaths to devicePaths: %+v. err: %+v", nodeVolumes, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for vm, volumes := range vmVolumes {
|
|
||||||
volumeAttachedMap := make(map[string]bool)
|
|
||||||
for _, volume := range volumes {
|
|
||||||
attached, err := diskIsAttached(ctx, volume, vm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
volumeAttachedMap[volume] = attached
|
|
||||||
}
|
|
||||||
disksAttached[vm] = volumeAttachedMap
|
|
||||||
}
|
|
||||||
return disksAttached, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// diskIsAttached returns if disk is attached to the VM using controllers supported by the plugin.
|
|
||||||
func diskIsAttached(ctx context.Context, volPath string, nodeName string) (bool, error) {
|
|
||||||
// Create context
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodeName)
|
|
||||||
Connect(ctx, nodeInfo.VSphere)
|
|
||||||
vm := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
|
|
||||||
volPath = removeStorageClusterORFolderNameFromVDiskPath(volPath)
|
|
||||||
device, err := getVirtualDeviceByPath(ctx, vm, volPath)
|
|
||||||
if err != nil {
|
|
||||||
framework.Logf("diskIsAttached failed to determine whether disk %q is still attached on node %q",
|
|
||||||
volPath,
|
|
||||||
nodeName)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if device == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
framework.Logf("diskIsAttached found the disk %q attached on node %q", volPath, nodeName)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUUIDFromProviderID strips ProviderPrefix - "vsphere://" from the providerID
|
|
||||||
// this gives the VM UUID which can be used to find Node VM from vCenter
|
|
||||||
func getUUIDFromProviderID(providerID string) string {
|
|
||||||
return strings.TrimPrefix(providerID, providerPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetReadySchedulableNodeInfos returns NodeInfo objects for all nodes with Ready and schedulable state
|
|
||||||
func GetReadySchedulableNodeInfos(ctx context.Context, c clientset.Interface) []*NodeInfo {
|
|
||||||
var nodesInfo []*NodeInfo
|
|
||||||
if TestContext.NodeMapper != nil {
|
|
||||||
nodeList, err := e2enode.GetReadySchedulableNodes(ctx, c)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
for _, node := range nodeList.Items {
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(node.Name)
|
|
||||||
if nodeInfo != nil {
|
|
||||||
nodesInfo = append(nodesInfo, nodeInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodesInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetReadySchedulableRandomNodeInfo returns NodeInfo object for one of the Ready and Schedulable Node.
|
|
||||||
// if multiple nodes are present with Ready and Schedulable state then one of the Node is selected randomly
|
|
||||||
// and it's associated NodeInfo object is returned.
|
|
||||||
func GetReadySchedulableRandomNodeInfo(ctx context.Context, c clientset.Interface) *NodeInfo {
|
|
||||||
nodesInfo := GetReadySchedulableNodeInfos(ctx, c)
|
|
||||||
gomega.Expect(nodesInfo).NotTo(gomega.BeEmpty())
|
|
||||||
return nodesInfo[rand.Int()%len(nodesInfo)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// invokeVCenterServiceControl invokes the given command for the given service
|
|
||||||
// via service-control on the given vCenter host over SSH.
|
|
||||||
func invokeVCenterServiceControl(ctx context.Context, command, service, host string) error {
|
|
||||||
sshCmd := fmt.Sprintf("service-control --%s %s", command, service)
|
|
||||||
framework.Logf("Invoking command %v on vCenter host %v", sshCmd, host)
|
|
||||||
result, err := e2essh.SSH(ctx, sshCmd, host, framework.TestContext.Provider)
|
|
||||||
if err != nil || result.Code != 0 {
|
|
||||||
e2essh.LogResult(result)
|
|
||||||
return fmt.Errorf("couldn't execute command: %s on vCenter host: %w", sshCmd, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectVolumeToBeAttached checks if the given Volume is attached to the given
|
|
||||||
// Node, else fails.
|
|
||||||
func expectVolumeToBeAttached(ctx context.Context, nodeName, volumePath string) {
|
|
||||||
isAttached, err := diskIsAttached(ctx, volumePath, nodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if !isAttached {
|
|
||||||
framework.Failf("Volume: %s is not attached to the node: %v", volumePath, nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectVolumesToBeAttached checks if the given Volumes are attached to the
|
|
||||||
// corresponding set of Nodes, else fails.
|
|
||||||
func expectVolumesToBeAttached(ctx context.Context, pods []*v1.Pod, volumePaths []string) {
|
|
||||||
for i, pod := range pods {
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
volumePath := volumePaths[i]
|
|
||||||
ginkgo.By(fmt.Sprintf("Verifying that volume %v is attached to node %v", volumePath, nodeName))
|
|
||||||
expectVolumeToBeAttached(ctx, nodeName, volumePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectFilesToBeAccessible checks if the given files are accessible on the
|
|
||||||
// corresponding set of Nodes, else fails.
|
|
||||||
func expectFilesToBeAccessible(namespace string, pods []*v1.Pod, filePaths []string) {
|
|
||||||
for i, pod := range pods {
|
|
||||||
podName := pod.Name
|
|
||||||
filePath := filePaths[i]
|
|
||||||
ginkgo.By(fmt.Sprintf("Verifying that file %v is accessible on pod %v", filePath, podName))
|
|
||||||
verifyFilesExistOnVSphereVolume(namespace, podName, filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeContentToPodFile writes the given content to the specified file.
|
|
||||||
func writeContentToPodFile(namespace, podName, filePath, content string) error {
|
|
||||||
_, err := e2ekubectl.RunKubectl(namespace, "exec", podName,
|
|
||||||
"--", "/bin/sh", "-c", fmt.Sprintf("echo '%s' > %s", content, filePath))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectFileContentToMatch checks if a given file contains the specified
|
|
||||||
// content, else fails.
|
|
||||||
func expectFileContentToMatch(namespace, podName, filePath, content string) {
|
|
||||||
_, err := e2ekubectl.RunKubectl(namespace, "exec", podName,
|
|
||||||
"--", "/bin/sh", "-c", fmt.Sprintf("grep '%s' %s", content, filePath))
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("failed to match content of file: %q on the pod: %q", filePath, podName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectFileContentsToMatch checks if the given contents match the ones present
|
|
||||||
// in corresponding files on respective Pods, else fails.
|
|
||||||
func expectFileContentsToMatch(namespace string, pods []*v1.Pod, filePaths []string, contents []string) {
|
|
||||||
for i, pod := range pods {
|
|
||||||
podName := pod.Name
|
|
||||||
filePath := filePaths[i]
|
|
||||||
ginkgo.By(fmt.Sprintf("Matching file content for %v on pod %v", filePath, podName))
|
|
||||||
expectFileContentToMatch(namespace, podName, filePath, contents[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Tests to verify volume provisioning on a clustered datastore
|
|
||||||
1. Static provisioning
|
|
||||||
2. Dynamic provisioning
|
|
||||||
3. Dynamic provisioning with spbm policy
|
|
||||||
|
|
||||||
This test reads env
|
|
||||||
1. CLUSTER_DATASTORE which should be set to clustered datastore
|
|
||||||
2. VSPHERE_SPBM_POLICY_DS_CLUSTER which should be set to a tag based spbm policy tagged to a clustered datastore
|
|
||||||
*/
|
|
||||||
var _ = utils.SIGDescribe("Volume Provisioning On Clustered Datastore", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-provision")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
scParameters map[string]string
|
|
||||||
clusterDatastore string
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
nodeInfo = GetReadySchedulableRandomNodeInfo(ctx, client)
|
|
||||||
scParameters = make(map[string]string)
|
|
||||||
clusterDatastore = GetAndExpectStringEnvVar(VCPClusterDatastore)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Steps:
|
|
||||||
1. Create volume options with datastore to be a clustered datastore
|
|
||||||
2. Create a vsphere volume
|
|
||||||
3. Create podspec with volume path. Create a corresponding pod
|
|
||||||
4. Verify disk is attached
|
|
||||||
5. Delete the pod and wait for the disk to be detached
|
|
||||||
6. Delete the volume
|
|
||||||
*/
|
|
||||||
|
|
||||||
ginkgo.It("verify static provisioning on clustered datastore", func(ctx context.Context) {
|
|
||||||
var volumePath string
|
|
||||||
|
|
||||||
ginkgo.By("creating a test vsphere volume")
|
|
||||||
volumeOptions := new(VolumeOptions)
|
|
||||||
volumeOptions.CapacityKB = 2097152
|
|
||||||
volumeOptions.Name = "e2e-vmdk-" + namespace
|
|
||||||
volumeOptions.Datastore = clusterDatastore
|
|
||||||
|
|
||||||
volumePath, err := nodeInfo.VSphere.CreateVolume(volumeOptions, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
ginkgo.By("Deleting the vsphere volume")
|
|
||||||
nodeInfo.VSphere.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
|
|
||||||
}()
|
|
||||||
|
|
||||||
podspec := getVSpherePodSpecWithVolumePaths([]string{volumePath}, nil, nil)
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod")
|
|
||||||
pod, err := client.CoreV1().Pods(namespace).Create(ctx, podspec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.By("Waiting for pod to be ready")
|
|
||||||
gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed())
|
|
||||||
|
|
||||||
// get fresh pod info
|
|
||||||
pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
|
|
||||||
ginkgo.By("Verifying volume is attached")
|
|
||||||
expectVolumeToBeAttached(ctx, nodeName, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
err = e2epod.DeletePodWithWait(ctx, client, pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for volumes to be detached from the node")
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, volumePath, nodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Steps:
|
|
||||||
1. Create storage class parameter and specify datastore to be a clustered datastore name
|
|
||||||
2. invokeValidPolicyTest - util to do e2e dynamic provision test
|
|
||||||
*/
|
|
||||||
ginkgo.It("verify dynamic provision with default parameter on clustered datastore", func(ctx context.Context) {
|
|
||||||
scParameters[Datastore] = clusterDatastore
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Steps:
|
|
||||||
1. Create storage class parameter and specify storage policy to be a tag based spbm policy
|
|
||||||
2. invokeValidPolicyTest - util to do e2e dynamic provision test
|
|
||||||
*/
|
|
||||||
ginkgo.It("verify dynamic provision with spbm policy on clustered datastore", func(ctx context.Context) {
|
|
||||||
policyDatastoreCluster := GetAndExpectStringEnvVar(SPBMPolicyDataStoreCluster)
|
|
||||||
scParameters[SpbmStoragePolicy] = policyDatastoreCluster
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
invalidDatastore = "invalidDatastore"
|
|
||||||
datastoreSCName = "datastoresc"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify datastore specified in storage-class is being honored while volume creation.
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create StorageClass with invalid datastore.
|
|
||||||
2. Create PVC which uses the StorageClass created in step 1.
|
|
||||||
3. Expect the PVC to fail.
|
|
||||||
4. Verify the error returned on PVC failure is the correct.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Volume Provisioning on Datastore", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-datastore")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
scParameters map[string]string
|
|
||||||
vSphereCSIMigrationEnabled bool
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
scParameters = make(map[string]string)
|
|
||||||
_, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
vSphereCSIMigrationEnabled = GetAndExpectBoolEnvVar(VSphereCSIMigrationEnabled)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify dynamically provisioned pv using storageclass fails on an invalid datastore", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for invalid datastore")
|
|
||||||
scParameters[Datastore] = invalidDatastore
|
|
||||||
scParameters[DiskFormat] = ThinDisk
|
|
||||||
err := invokeInvalidDatastoreTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
var errorMsg string
|
|
||||||
if !vSphereCSIMigrationEnabled {
|
|
||||||
errorMsg = `Failed to provision volume with StorageClass \"` + datastoreSCName + `\": Datastore '` + invalidDatastore + `' not found`
|
|
||||||
} else {
|
|
||||||
errorMsg = `failed to find datastoreURL for datastore name: \"` + invalidDatastore + `\"`
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func invokeInvalidDatastoreTestNeg(ctx context.Context, client clientset.Interface, namespace string, scParameters map[string]string) error {
|
|
||||||
ginkgo.By("Creating Storage Class With Invalid Datastore")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(datastoreSCName, scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
ginkgo.By("Expect claim to fail provisioning volume")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, pvclaim.Namespace, pvclaim.Name, framework.Poll, 2*time.Minute)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
|
|
||||||
eventList, err := client.CoreV1().Events(pvclaim.Namespace).List(ctx, metav1.ListOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
var eventErrorMessages string
|
|
||||||
for _, event := range eventList.Items {
|
|
||||||
if event.Type != v1.EventTypeNormal {
|
|
||||||
eventErrorMessages = eventErrorMessages + event.Message + ";"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("event messages: %+q", eventErrorMessages)
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify diskformat specified in storage-class is being honored while volume creation.
|
|
||||||
Valid and supported options are eagerzeroedthick, zeroedthick and thin
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create StorageClass with diskformat set to valid type
|
|
||||||
2. Create PVC which uses the StorageClass created in step 1.
|
|
||||||
3. Wait for PV to be provisioned.
|
|
||||||
4. Wait for PVC's status to become Bound
|
|
||||||
5. Create pod using PVC on specific node.
|
|
||||||
6. Wait for Disk to be attached to the node.
|
|
||||||
7. Get node VM's devices and find PV's Volume Disk.
|
|
||||||
8. Get Backing Info of the Volume Disk and obtain EagerlyScrub and ThinProvisioned
|
|
||||||
9. Based on the value of EagerlyScrub and ThinProvisioned, verify diskformat is correct.
|
|
||||||
10. Delete pod and Wait for Volume Disk to be detached from the Node.
|
|
||||||
11. Delete PVC, PV and Storage Class
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Volume Disk Format", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-disk-format")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
const (
|
|
||||||
NodeLabelKey = "vsphere_e2e_label_volume_diskformat"
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
nodeName string
|
|
||||||
nodeKeyValueLabel map[string]string
|
|
||||||
nodeLabelValue string
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
nodeName = GetReadySchedulableRandomNodeInfo(ctx, client).Name
|
|
||||||
nodeLabelValue = "vsphere_e2e_" + string(uuid.NewUUID())
|
|
||||||
nodeKeyValueLabel = map[string]string{NodeLabelKey: nodeLabelValue}
|
|
||||||
e2enode.AddOrUpdateLabelOnNode(client, nodeName, NodeLabelKey, nodeLabelValue)
|
|
||||||
ginkgo.DeferCleanup(e2enode.RemoveLabelOffNode, client, nodeName, NodeLabelKey)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify disk format type - eagerzeroedthick is honored for dynamically provisioned pv using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for diskformat: eagerzeroedthick")
|
|
||||||
invokeTest(ctx, f, client, namespace, nodeName, nodeKeyValueLabel, "eagerzeroedthick")
|
|
||||||
})
|
|
||||||
ginkgo.It("verify disk format type - zeroedthick is honored for dynamically provisioned pv using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for diskformat: zeroedthick")
|
|
||||||
invokeTest(ctx, f, client, namespace, nodeName, nodeKeyValueLabel, "zeroedthick")
|
|
||||||
})
|
|
||||||
ginkgo.It("verify disk format type - thin is honored for dynamically provisioned pv using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for diskformat: thin")
|
|
||||||
invokeTest(ctx, f, client, namespace, nodeName, nodeKeyValueLabel, "thin")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func invokeTest(ctx context.Context, f *framework.Framework, client clientset.Interface, namespace string, nodeName string, nodeKeyValueLabel map[string]string, diskFormat string) {
|
|
||||||
|
|
||||||
framework.Logf("Invoking Test for DiskFomat: %s", diskFormat)
|
|
||||||
scParameters := make(map[string]string)
|
|
||||||
scParameters["diskformat"] = diskFormat
|
|
||||||
|
|
||||||
ginkgo.By("Creating Storage Class With DiskFormat")
|
|
||||||
storageClassSpec := getVSphereStorageClassSpec("thinsc", scParameters, nil, "")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, storageClassSpec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaimSpec := getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass)
|
|
||||||
pvclaim, err := client.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, pvclaimSpec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.CoreV1().PersistentVolumeClaims(namespace).Delete), pvclaimSpec.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, pvclaim.Namespace, pvclaim.Name, framework.Poll, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Get new copy of the claim
|
|
||||||
pvclaim, err = client.CoreV1().PersistentVolumeClaims(pvclaim.Namespace).Get(ctx, pvclaim.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Get the bound PV
|
|
||||||
pv, err := client.CoreV1().PersistentVolumes().Get(ctx, pvclaim.Spec.VolumeName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
/*
|
|
||||||
PV is required to be attached to the Node. so that using govmomi API we can grab Disk's Backing Info
|
|
||||||
to check EagerlyScrub and ThinProvisioned property
|
|
||||||
*/
|
|
||||||
ginkgo.By("Creating pod to attach PV to the node")
|
|
||||||
// Create pod to attach Volume to Node
|
|
||||||
podSpec := getVSpherePodSpecWithClaim(pvclaim.Name, nodeKeyValueLabel, "while true ; do sleep 2 ; done")
|
|
||||||
pod, err := client.CoreV1().Pods(namespace).Create(ctx, podSpec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for pod to be running")
|
|
||||||
gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed())
|
|
||||||
|
|
||||||
isAttached, err := diskIsAttached(ctx, pv.Spec.VsphereVolume.VolumePath, nodeName)
|
|
||||||
if !isAttached {
|
|
||||||
framework.Failf("Volume: %s is not attached to the node: %v", pv.Spec.VsphereVolume.VolumePath, nodeName)
|
|
||||||
}
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verify Disk Format")
|
|
||||||
if !verifyDiskFormat(ctx, client, nodeName, pv.Spec.VsphereVolume.VolumePath, diskFormat) {
|
|
||||||
framework.Failf("DiskFormat Verification Failed. Node: %s, VolumePath: %s, Expected Format: %s", nodeName, pv.Spec.VsphereVolume.VolumePath, diskFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
var volumePaths []string
|
|
||||||
volumePaths = append(volumePaths, pv.Spec.VsphereVolume.VolumePath)
|
|
||||||
|
|
||||||
ginkgo.By("Delete pod and wait for volume to be detached from node")
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, client, pod, nodeName, volumePaths)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyDiskFormat(ctx context.Context, client clientset.Interface, nodeName string, pvVolumePath string, diskFormat string) bool {
|
|
||||||
ginkgo.By("Verifying disk format")
|
|
||||||
eagerlyScrub := false
|
|
||||||
thinProvisioned := false
|
|
||||||
diskFound := false
|
|
||||||
pvvmdkfileName := filepath.Base(pvVolumePath) + filepath.Ext(pvVolumePath)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodeName)
|
|
||||||
vm := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
|
|
||||||
vmDevices, err := vm.Device(ctx)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
disks := vmDevices.SelectByType((*types.VirtualDisk)(nil))
|
|
||||||
|
|
||||||
for _, disk := range disks {
|
|
||||||
backing := disk.GetVirtualDevice().Backing.(*types.VirtualDiskFlatVer2BackingInfo)
|
|
||||||
backingFileName := filepath.Base(backing.FileName) + filepath.Ext(backing.FileName)
|
|
||||||
if backingFileName == pvvmdkfileName {
|
|
||||||
diskFound = true
|
|
||||||
if backing.EagerlyScrub != nil {
|
|
||||||
eagerlyScrub = *backing.EagerlyScrub
|
|
||||||
}
|
|
||||||
if backing.ThinProvisioned != nil {
|
|
||||||
thinProvisioned = *backing.ThinProvisioned
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !diskFound {
|
|
||||||
framework.Failf("Failed to find disk: %s", pvVolumePath)
|
|
||||||
}
|
|
||||||
isDiskFormatCorrect := false
|
|
||||||
if diskFormat == "eagerzeroedthick" {
|
|
||||||
if eagerlyScrub && !thinProvisioned {
|
|
||||||
isDiskFormatCorrect = true
|
|
||||||
}
|
|
||||||
} else if diskFormat == "zeroedthick" {
|
|
||||||
if !eagerlyScrub && !thinProvisioned {
|
|
||||||
isDiskFormatCorrect = true
|
|
||||||
}
|
|
||||||
} else if diskFormat == "thin" {
|
|
||||||
if !eagerlyScrub && thinProvisioned {
|
|
||||||
isDiskFormatCorrect = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isDiskFormatCorrect
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
diskSizeSCName = "disksizesc"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify disk size specified in PVC is being rounded up correctly.
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create StorageClass.
|
|
||||||
2. Create PVC with invalid disk size which uses the StorageClass created in step 1.
|
|
||||||
3. Verify the provisioned PV size is correct.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Volume Disk Size", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-disksize")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
scParameters map[string]string
|
|
||||||
datastore string
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func() {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
scParameters = make(map[string]string)
|
|
||||||
datastore = GetAndExpectStringEnvVar(StorageClassDatastoreName)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify dynamically provisioned pv has size rounded up correctly", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test disk size")
|
|
||||||
scParameters[Datastore] = datastore
|
|
||||||
scParameters[DiskFormat] = ThinDisk
|
|
||||||
diskSize := "1"
|
|
||||||
expectedDiskSize := "1Mi"
|
|
||||||
|
|
||||||
ginkgo.By("Creating Storage Class")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(diskSizeSCName, scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, diskSize, storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, pvclaim.Namespace, pvclaim.Name, framework.Poll, 2*time.Minute)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Getting new copy of PVC")
|
|
||||||
pvclaim, err = client.CoreV1().PersistentVolumeClaims(pvclaim.Namespace).Get(ctx, pvclaim.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Getting PV created")
|
|
||||||
pv, err := client.CoreV1().PersistentVolumes().Get(ctx, pvclaim.Spec.VolumeName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verifying if provisioned PV has the correct size")
|
|
||||||
expectedCapacity := resource.MustParse(expectedDiskSize)
|
|
||||||
pvCapacity := pv.Spec.Capacity[v1.ResourceName(v1.ResourceStorage)]
|
|
||||||
gomega.Expect(pvCapacity.Value()).To(gomega.Equal(expectedCapacity.Value()))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ext4FSType = "ext4"
|
|
||||||
ext3FSType = "ext3"
|
|
||||||
invalidFSType = "ext10"
|
|
||||||
execCommand = "/bin/df -T /mnt/volume1 | /bin/awk 'FNR == 2 {print $2}' > /mnt/volume1/fstype && while true ; do sleep 2 ; done"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify fstype specified in storage-class is being honored after volume creation.
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create StorageClass with fstype set to valid type (default case included).
|
|
||||||
2. Create PVC which uses the StorageClass created in step 1.
|
|
||||||
3. Wait for PV to be provisioned.
|
|
||||||
4. Wait for PVC's status to become Bound.
|
|
||||||
5. Create pod using PVC on specific node.
|
|
||||||
6. Wait for Disk to be attached to the node.
|
|
||||||
7. Execute command in the pod to get fstype.
|
|
||||||
8. Delete pod and Wait for Volume Disk to be detached from the Node.
|
|
||||||
9. Delete PVC, PV and Storage Class.
|
|
||||||
|
|
||||||
Test to verify if an invalid fstype specified in storage class fails pod creation.
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create StorageClass with invalid.
|
|
||||||
2. Create PVC which uses the StorageClass created in step 1.
|
|
||||||
3. Wait for PV to be provisioned.
|
|
||||||
4. Wait for PVC's status to become Bound.
|
|
||||||
5. Create pod using PVC.
|
|
||||||
6. Verify if the pod creation fails.
|
|
||||||
7. Verify if the MountVolume.MountDevice fails because it is unable to find the file system executable file on the node.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Volume FStype", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-fstype")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
gomega.Expect(GetReadySchedulableNodeInfos(ctx, client)).NotTo(gomega.BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify fstype - ext3 formatted volume", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for fstype: ext3")
|
|
||||||
invokeTestForFstype(ctx, f, ext3FSType, ext3FSType)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify fstype - default value should be ext4", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for fstype: Default Value - ext4")
|
|
||||||
invokeTestForFstype(ctx, f, "", ext4FSType)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify invalid fstype", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Invoking Test for fstype: invalid Value")
|
|
||||||
invokeTestForInvalidFstype(ctx, f, client, invalidFSType)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func invokeTestForFstype(ctx context.Context, f *framework.Framework, fstype string, expectedContent string) {
|
|
||||||
framework.Logf("Invoking Test for fstype: %s", fstype)
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
scParameters := make(map[string]string)
|
|
||||||
scParameters["fstype"] = fstype
|
|
||||||
|
|
||||||
// Create Persistent Volume
|
|
||||||
ginkgo.By("Creating Storage Class With Fstype")
|
|
||||||
pvclaim, persistentvolumes := createVolume(ctx, f.ClientSet, f.Timeouts, f.Namespace.Name, scParameters)
|
|
||||||
|
|
||||||
// Create Pod and verify the persistent volume is accessible
|
|
||||||
pod := createPodAndVerifyVolumeAccessible(ctx, f, pvclaim, persistentvolumes)
|
|
||||||
_, err := e2eoutput.LookForStringInPodExec(namespace, pod.Name, []string{"/bin/cat", "/mnt/volume1/fstype"}, expectedContent, time.Minute)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Detach and delete volume
|
|
||||||
detachVolume(ctx, f, pod, persistentvolumes[0].Spec.VsphereVolume.VolumePath)
|
|
||||||
err = e2epv.DeletePersistentVolumeClaim(ctx, f.ClientSet, pvclaim.Name, namespace)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func invokeTestForInvalidFstype(ctx context.Context, f *framework.Framework, client clientset.Interface, fstype string) {
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
scParameters := make(map[string]string)
|
|
||||||
scParameters["fstype"] = fstype
|
|
||||||
|
|
||||||
// Create Persistent Volume
|
|
||||||
ginkgo.By("Creating Storage Class With Invalid Fstype")
|
|
||||||
pvclaim, persistentvolumes := createVolume(ctx, client, f.Timeouts, namespace, scParameters)
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod to attach PV to the node")
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
// Create pod to attach Volume to Node
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, nil, pvclaims, f.NamespacePodSecurityLevel, execCommand)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
|
|
||||||
eventList, err := client.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Detach and delete volume
|
|
||||||
detachVolume(ctx, f, pod, persistentvolumes[0].Spec.VsphereVolume.VolumePath)
|
|
||||||
err = e2epv.DeletePersistentVolumeClaim(ctx, client, pvclaim.Name, namespace)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
gomega.Expect(eventList.Items).NotTo(gomega.BeEmpty())
|
|
||||||
errorMsg := `MountVolume.MountDevice failed for volume "` + persistentvolumes[0].Name + `" : executable file not found`
|
|
||||||
isFound := false
|
|
||||||
for _, item := range eventList.Items {
|
|
||||||
if strings.Contains(item.Message, errorMsg) {
|
|
||||||
isFound = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isFound {
|
|
||||||
framework.Failf("Unable to verify MountVolume.MountDevice failure for volume %s", persistentvolumes[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVolume(ctx context.Context, client clientset.Interface, timeouts *framework.TimeoutContext, namespace string, scParameters map[string]string) (*v1.PersistentVolumeClaim, []*v1.PersistentVolume) {
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("fstype", scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := client.CoreV1().PersistentVolumeClaims(namespace).Create(ctx, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
return pvclaim, persistentvolumes
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPodAndVerifyVolumeAccessible(ctx context.Context, f *framework.Framework, pvclaim *v1.PersistentVolumeClaim, persistentvolumes []*v1.PersistentVolume) *v1.Pod {
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
ginkgo.By("Creating pod to attach PV to the node")
|
|
||||||
// Create pod to attach Volume to Node
|
|
||||||
pod, err := e2epod.CreatePod(ctx, f.ClientSet, f.Namespace.Name, nil, pvclaims, f.NamespacePodSecurityLevel, execCommand)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Asserts: Right disk is attached to the pod
|
|
||||||
ginkgo.By("Verify the volume is accessible and available in the pod")
|
|
||||||
verifyVSphereVolumesAccessible(ctx, f.ClientSet, pod, persistentvolumes)
|
|
||||||
return pod
|
|
||||||
}
|
|
||||||
|
|
||||||
// detachVolume delete the volume passed in the argument and wait until volume is detached from the node,
|
|
||||||
func detachVolume(ctx context.Context, f *framework.Framework, pod *v1.Pod, volPath string) {
|
|
||||||
pod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
err = e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for volumes to be detached from the node")
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, volPath, nodeName))
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/pkg/cluster/ports"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// waitForKubeletUp waits for the kubelet on the given host to be up.
|
|
||||||
func waitForKubeletUp(ctx context.Context, host string) error {
|
|
||||||
cmd := "curl http://localhost:" + strconv.Itoa(ports.KubeletReadOnlyPort) + "/healthz"
|
|
||||||
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
|
|
||||||
result, err := e2essh.SSH(ctx, cmd, host, framework.TestContext.Provider)
|
|
||||||
if err != nil || result.Code != 0 {
|
|
||||||
e2essh.LogResult(result)
|
|
||||||
}
|
|
||||||
if result.Stdout == "ok" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("waiting for kubelet timed out")
|
|
||||||
}
|
|
||||||
|
|
||||||
// restartKubelet restarts kubelet on the given host.
|
|
||||||
func restartKubelet(ctx context.Context, host string) error {
|
|
||||||
var cmd string
|
|
||||||
|
|
||||||
var sudoPresent bool
|
|
||||||
sshResult, err := e2essh.SSH(ctx, "sudo --version", host, framework.TestContext.Provider)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to ssh to host %s with error %v", host, err)
|
|
||||||
}
|
|
||||||
if !strings.Contains(sshResult.Stderr, "command not found") {
|
|
||||||
sudoPresent = true
|
|
||||||
}
|
|
||||||
sshResult, err = e2essh.SSH(ctx, "systemctl --version", host, framework.TestContext.Provider)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to execute command 'systemctl' on host %s with error %v", host, err)
|
|
||||||
}
|
|
||||||
if !strings.Contains(sshResult.Stderr, "command not found") {
|
|
||||||
cmd = "systemctl restart kubelet"
|
|
||||||
} else {
|
|
||||||
cmd = "service kubelet restart"
|
|
||||||
}
|
|
||||||
if sudoPresent {
|
|
||||||
cmd = fmt.Sprintf("sudo %s", cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
framework.Logf("Restarting kubelet via ssh on host %s with command %s", host, cmd)
|
|
||||||
result, err := e2essh.SSH(ctx, cmd, host, framework.TestContext.Provider)
|
|
||||||
if err != nil || result.Code != 0 {
|
|
||||||
e2essh.LogResult(result)
|
|
||||||
return fmt.Errorf("couldn't restart kubelet: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify volume remains attached after kubelet restart on master node
|
|
||||||
For the number of schedulable nodes,
|
|
||||||
1. Create a volume with default volume options
|
|
||||||
2. Create a Pod
|
|
||||||
3. Verify the volume is attached
|
|
||||||
4. Restart the kubelet on master node
|
|
||||||
5. Verify again that the volume is attached
|
|
||||||
6. Delete the pod and wait for the volume to be detached
|
|
||||||
7. Delete the volume
|
|
||||||
*/
|
|
||||||
var _ = utils.SIGDescribe("Volume Attach Verify", feature.Vsphere, framework.WithSerial(), framework.WithDisruptive(), func() {
|
|
||||||
f := framework.NewDefaultFramework("restart-master")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
|
|
||||||
const labelKey = "vsphere_e2e_label"
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
volumePaths []string
|
|
||||||
pods []*v1.Pod
|
|
||||||
numNodes int
|
|
||||||
nodeKeyValueLabelList []map[string]string
|
|
||||||
nodeNameList []string
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, client, f.Timeouts.NodeSchedulable))
|
|
||||||
|
|
||||||
nodes, err := e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
numNodes = len(nodes.Items)
|
|
||||||
if numNodes < 2 {
|
|
||||||
e2eskipper.Skipf("Requires at least %d nodes (not %d)", 2, len(nodes.Items))
|
|
||||||
}
|
|
||||||
nodeInfo = TestContext.NodeMapper.GetNodeInfo(nodes.Items[0].Name)
|
|
||||||
for i := 0; i < numNodes; i++ {
|
|
||||||
nodeName := nodes.Items[i].Name
|
|
||||||
nodeNameList = append(nodeNameList, nodeName)
|
|
||||||
nodeLabelValue := "vsphere_e2e_" + string(uuid.NewUUID())
|
|
||||||
nodeKeyValueLabel := make(map[string]string)
|
|
||||||
nodeKeyValueLabel[labelKey] = nodeLabelValue
|
|
||||||
nodeKeyValueLabelList = append(nodeKeyValueLabelList, nodeKeyValueLabel)
|
|
||||||
e2enode.AddOrUpdateLabelOnNode(client, nodeName, labelKey, nodeLabelValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify volume remains attached after master kubelet restart", func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessSSHKeyPresent()
|
|
||||||
|
|
||||||
// Create pod on each node
|
|
||||||
for i := 0; i < numNodes; i++ {
|
|
||||||
ginkgo.By(fmt.Sprintf("%d: Creating a test vsphere volume", i))
|
|
||||||
volumePath, err := nodeInfo.VSphere.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePaths = append(volumePaths, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod %d on node %v", i, nodeNameList[i]))
|
|
||||||
podspec := getVSpherePodSpecWithVolumePaths([]string{volumePath}, nodeKeyValueLabelList[i], nil)
|
|
||||||
pod, err := client.CoreV1().Pods(namespace).Create(ctx, podspec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epod.DeletePodWithWait, client, pod)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for pod to be ready")
|
|
||||||
gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed())
|
|
||||||
|
|
||||||
pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
pods = append(pods, pod)
|
|
||||||
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
ginkgo.By(fmt.Sprintf("Verify volume %s is attached to the node %s", volumePath, nodeName))
|
|
||||||
expectVolumeToBeAttached(ctx, nodeName, volumePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Restarting kubelet on instance node")
|
|
||||||
instanceAddress := framework.APIAddress() + ":22"
|
|
||||||
err := restartKubelet(ctx, instanceAddress)
|
|
||||||
framework.ExpectNoError(err, "Unable to restart kubelet on instance node")
|
|
||||||
|
|
||||||
ginkgo.By("Verifying the kubelet on instance node is up")
|
|
||||||
err = waitForKubeletUp(ctx, instanceAddress)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
for i, pod := range pods {
|
|
||||||
volumePath := volumePaths[i]
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("After master restart, verify volume %v is attached to the node %v", volumePath, nodeName))
|
|
||||||
expectVolumeToBeAttached(ctx, nodeName, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Deleting pod on node %s", nodeName))
|
|
||||||
err = e2epod.DeletePodWithWait(ctx, client, pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Waiting for volume %s to be detached from the node %s", volumePath, nodeName))
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, volumePath, nodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Deleting volume %s", volumePath))
|
|
||||||
err = nodeInfo.VSphere.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Node Unregister", feature.Vsphere, framework.WithSlow(), framework.WithDisruptive(), func() {
|
|
||||||
f := framework.NewDefaultFramework("node-unregister")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
workingDir string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, client, f.Timeouts.NodeSchedulable))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
workingDir = GetAndExpectStringEnvVar("VSPHERE_WORKING_DIR")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("node unregister", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Get total Ready nodes")
|
|
||||||
nodeList, err := e2enode.GetReadySchedulableNodes(ctx, f.ClientSet)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if len(nodeList.Items) < 2 {
|
|
||||||
framework.Failf("At least 2 nodes are required for this test, got instead: %v", len(nodeList.Items))
|
|
||||||
}
|
|
||||||
|
|
||||||
totalNodesCount := len(nodeList.Items)
|
|
||||||
nodeVM := nodeList.Items[0]
|
|
||||||
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodeVM.ObjectMeta.Name)
|
|
||||||
vmObject := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
|
|
||||||
|
|
||||||
// Find VM .vmx file path, host, resource pool.
|
|
||||||
// They are required to register a node VM to VC
|
|
||||||
vmxFilePath := getVMXFilePath(ctx, vmObject)
|
|
||||||
|
|
||||||
vmHost, err := vmObject.HostSystem(ctx)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
vmPool, err := vmObject.ResourcePool(ctx)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Unregister Node VM
|
|
||||||
ginkgo.By("Unregister a node VM")
|
|
||||||
unregisterNodeVM(ctx, nodeVM.ObjectMeta.Name, vmObject)
|
|
||||||
|
|
||||||
// Ready nodes should be 1 less
|
|
||||||
ginkgo.By("Verifying the ready node counts")
|
|
||||||
if !verifyReadyNodeCount(ctx, f.ClientSet, totalNodesCount-1) {
|
|
||||||
framework.Failf("Unable to verify expected ready node count. Total Nodes: %d, Expected Ready Nodes: %d", totalNodesCount, totalNodesCount-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeList, err = e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
var nodeNameList []string
|
|
||||||
for _, node := range nodeList.Items {
|
|
||||||
nodeNameList = append(nodeNameList, node.ObjectMeta.Name)
|
|
||||||
}
|
|
||||||
gomega.Expect(nodeNameList).NotTo(gomega.ContainElement(nodeVM.ObjectMeta.Name))
|
|
||||||
|
|
||||||
// Register Node VM
|
|
||||||
ginkgo.By("Register back the node VM")
|
|
||||||
registerNodeVM(ctx, nodeVM.ObjectMeta.Name, workingDir, vmxFilePath, vmPool, vmHost)
|
|
||||||
|
|
||||||
// Ready nodes should be equal to earlier count
|
|
||||||
ginkgo.By("Verifying the ready node counts")
|
|
||||||
if !verifyReadyNodeCount(ctx, f.ClientSet, totalNodesCount) {
|
|
||||||
framework.Failf("Unable to verify expected ready node count. Total Nodes: %d, Expected Ready Nodes: %d", totalNodesCount, totalNodesCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeList, err = e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
nodeNameList = nodeNameList[:0]
|
|
||||||
for _, node := range nodeList.Items {
|
|
||||||
nodeNameList = append(nodeNameList, node.ObjectMeta.Name)
|
|
||||||
}
|
|
||||||
gomega.Expect(nodeNameList).To(gomega.ContainElement(nodeVM.ObjectMeta.Name))
|
|
||||||
|
|
||||||
// Sanity test that pod provisioning works
|
|
||||||
ginkgo.By("Sanity check for volume lifecycle")
|
|
||||||
scParameters := make(map[string]string)
|
|
||||||
storagePolicy := GetAndExpectStringEnvVar("VSPHERE_SPBM_GOLD_POLICY")
|
|
||||||
scParameters[SpbmStoragePolicy] = storagePolicy
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
"github.com/vmware/govmomi/object"
|
|
||||||
vimtypes "github.com/vmware/govmomi/vim25/types"
|
|
||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify volume status after node power off:
|
|
||||||
1. Verify the pod got provisioned on a different node with volume attached to it
|
|
||||||
2. Verify the volume is detached from the powered off node
|
|
||||||
*/
|
|
||||||
var _ = utils.SIGDescribe("Node Poweroff", feature.Vsphere, framework.WithSlow(), framework.WithDisruptive(), func() {
|
|
||||||
f := framework.NewDefaultFramework("node-poweroff")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, client, f.Timeouts.NodeSchedulable))
|
|
||||||
nodeList, err := e2enode.GetReadySchedulableNodes(ctx, f.ClientSet)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if len(nodeList.Items) < 2 {
|
|
||||||
framework.Failf("At least 2 nodes are required for this test, got instead: %v", len(nodeList.Items))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Steps:
|
|
||||||
1. Create a StorageClass
|
|
||||||
2. Create a PVC with the StorageClass
|
|
||||||
3. Create a Deployment with 1 replica, using the PVC
|
|
||||||
4. Verify the pod got provisioned on a node
|
|
||||||
5. Verify the volume is attached to the node
|
|
||||||
6. Power off the node where pod got provisioned
|
|
||||||
7. Verify the pod got provisioned on a different node
|
|
||||||
8. Verify the volume is attached to the new node
|
|
||||||
9. Verify the volume is detached from the old node
|
|
||||||
10. Delete the Deployment and wait for the volume to be detached
|
|
||||||
11. Delete the PVC
|
|
||||||
12. Delete the StorageClass
|
|
||||||
*/
|
|
||||||
ginkgo.It("verify volume status after node power off", func(ctx context.Context) {
|
|
||||||
ginkgo.By("Creating a Storage Class")
|
|
||||||
storageClassSpec := getVSphereStorageClassSpec("test-sc", nil, nil, "")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, storageClassSpec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaimSpec := getVSphereClaimSpecWithStorageClass(namespace, "1Gi", storageclass)
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, pvclaimSpec)
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create PVC with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for PVC to be in bound phase")
|
|
||||||
pvclaims := []*v1.PersistentVolumeClaim{pvclaim}
|
|
||||||
pvs, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to wait until PVC phase set to bound: %v", err))
|
|
||||||
volumePath := pvs[0].Spec.VsphereVolume.VolumePath
|
|
||||||
|
|
||||||
ginkgo.By("Creating a Deployment")
|
|
||||||
deployment, err := e2edeployment.CreateDeployment(ctx, client, int32(1), map[string]string{"test": "app"}, nil, namespace, pvclaims, admissionapi.LevelRestricted, "")
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create Deployment with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AppsV1().Deployments(namespace).Delete), deployment.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Get pod from the deployment")
|
|
||||||
podList, err := e2edeployment.GetPodsForDeployment(ctx, client, deployment)
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to get pod from the deployment with err: %v", err))
|
|
||||||
gomega.Expect(podList.Items).NotTo(gomega.BeEmpty())
|
|
||||||
pod := podList.Items[0]
|
|
||||||
node1 := pod.Spec.NodeName
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Verify disk is attached to the node: %v", node1))
|
|
||||||
isAttached, err := diskIsAttached(ctx, volumePath, node1)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if !isAttached {
|
|
||||||
framework.Failf("Volume: %s is not attached to the node: %v", volumePath, node1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Power off the node: %v", node1))
|
|
||||||
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(node1)
|
|
||||||
vm := object.NewVirtualMachine(nodeInfo.VSphere.Client.Client, nodeInfo.VirtualMachineRef)
|
|
||||||
_, err = vm.PowerOff(ctx)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(vm.PowerOn)
|
|
||||||
|
|
||||||
err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOff)
|
|
||||||
framework.ExpectNoError(err, "Unable to power off the node")
|
|
||||||
|
|
||||||
// Waiting for the pod to be failed over to a different node
|
|
||||||
node2, err := waitForPodToFailover(ctx, client, deployment, node1)
|
|
||||||
framework.ExpectNoError(err, "Pod did not fail over to a different node")
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Waiting for disk to be attached to the new node: %v", node2))
|
|
||||||
err = waitForVSphereDiskToAttach(ctx, volumePath, node2)
|
|
||||||
framework.ExpectNoError(err, "Disk is not attached to the node")
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Waiting for disk to be detached from the previous node: %v", node1))
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, volumePath, node1)
|
|
||||||
framework.ExpectNoError(err, "Disk is not detached from the node")
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Power on the previous node: %v", node1))
|
|
||||||
vm.PowerOn(ctx)
|
|
||||||
err = vm.WaitForPowerState(ctx, vimtypes.VirtualMachinePowerStatePoweredOn)
|
|
||||||
framework.ExpectNoError(err, "Unable to power on the node")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait until the pod failed over to a different node, or time out after 3 minutes
|
|
||||||
func waitForPodToFailover(ctx context.Context, client clientset.Interface, deployment *appsv1.Deployment, oldNode string) (string, error) {
|
|
||||||
var (
|
|
||||||
timeout = 3 * time.Minute
|
|
||||||
pollTime = 10 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
waitErr := wait.PollWithContext(ctx, pollTime, timeout, func(ctx context.Context) (bool, error) {
|
|
||||||
currentNode, err := getNodeForDeployment(ctx, client, deployment)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentNode != oldNode {
|
|
||||||
framework.Logf("The pod has been failed over from %q to %q", oldNode, currentNode)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
framework.Logf("Waiting for pod to be failed over from %q", oldNode)
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if waitErr != nil {
|
|
||||||
if wait.Interrupted(waitErr) {
|
|
||||||
return "", fmt.Errorf("pod has not failed over after %v: %v", timeout, waitErr)
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("pod did not fail over from %q: %v", oldNode, waitErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getNodeForDeployment(ctx, client, deployment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNodeForDeployment returns node name for the Deployment
|
|
||||||
func getNodeForDeployment(ctx context.Context, client clientset.Interface, deployment *appsv1.Deployment) (string, error) {
|
|
||||||
podList, err := e2edeployment.GetPodsForDeployment(ctx, client, deployment)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return podList.Items[0].Spec.NodeName, nil
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to perform Disk Ops storm.
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create storage class for thin Provisioning.
|
|
||||||
2. Create 30 PVCs using above storage class in annotation, requesting 2 GB files.
|
|
||||||
3. Wait until all disks are ready and all PVs and PVCs get bind. (CreateVolume storm)
|
|
||||||
4. Create pod to mount volumes using PVCs created in step 2. (AttachDisk storm)
|
|
||||||
5. Wait for pod status to be running.
|
|
||||||
6. Verify all volumes accessible and available in the pod.
|
|
||||||
7. Delete pod.
|
|
||||||
8. wait until volumes gets detached. (DetachDisk storm)
|
|
||||||
9. Delete all PVCs. This should delete all Disks. (DeleteVolume storm)
|
|
||||||
10. Delete storage class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Volume Operations Storm", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-ops-storm")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
const defaultVolumeOpsScale = 30
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
storageclass *storagev1.StorageClass
|
|
||||||
pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
persistentvolumes []*v1.PersistentVolume
|
|
||||||
err error
|
|
||||||
volumeOpsScale int
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
gomega.Expect(GetReadySchedulableNodeInfos(ctx, client)).NotTo(gomega.BeEmpty())
|
|
||||||
if scale := os.Getenv("VOLUME_OPS_SCALE"); scale != "" {
|
|
||||||
volumeOpsScale, err = strconv.Atoi(scale)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
} else {
|
|
||||||
volumeOpsScale = defaultVolumeOpsScale
|
|
||||||
}
|
|
||||||
pvclaims = make([]*v1.PersistentVolumeClaim, volumeOpsScale)
|
|
||||||
})
|
|
||||||
ginkgo.AfterEach(func(ctx context.Context) {
|
|
||||||
ginkgo.By("Deleting PVCs")
|
|
||||||
for _, claim := range pvclaims {
|
|
||||||
_ = e2epv.DeletePersistentVolumeClaim(ctx, client, claim.Name, namespace)
|
|
||||||
}
|
|
||||||
ginkgo.By("Deleting StorageClass")
|
|
||||||
err = client.StorageV1().StorageClasses().Delete(ctx, storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("should create pod with many volumes and verify no attach call fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Running test with VOLUME_OPS_SCALE: %v", volumeOpsScale))
|
|
||||||
ginkgo.By("Creating Storage Class")
|
|
||||||
scParameters := make(map[string]string)
|
|
||||||
scParameters["diskformat"] = "thin"
|
|
||||||
storageclass, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("thinsc", scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVCs using the Storage Class")
|
|
||||||
count := 0
|
|
||||||
for count < volumeOpsScale {
|
|
||||||
pvclaims[count], err = e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for all claims to be in bound phase")
|
|
||||||
persistentvolumes, err = e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod to attach PVs to the node")
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, nil, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verify all volumes are accessible and available in the pod")
|
|
||||||
verifyVSphereVolumesAccessible(ctx, client, pod, persistentvolumes)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, client, pod))
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for volumes to be detached from the node")
|
|
||||||
for _, pv := range persistentvolumes {
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, pv.Spec.VsphereVolume.VolumePath, pod.Spec.NodeName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test calculates latency numbers for volume lifecycle operations
|
|
||||||
|
|
||||||
1. Create 4 type of storage classes
|
|
||||||
2. Read the total number of volumes to be created and volumes per pod
|
|
||||||
3. Create total PVCs (number of volumes)
|
|
||||||
4. Create Pods with attached volumes per pod
|
|
||||||
5. Verify access to the volumes
|
|
||||||
6. Delete pods and wait for volumes to detach
|
|
||||||
7. Delete the PVCs
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
SCSIUnitsAvailablePerNode = 55
|
|
||||||
CreateOp = "CreateOp"
|
|
||||||
AttachOp = "AttachOp"
|
|
||||||
DetachOp = "DetachOp"
|
|
||||||
DeleteOp = "DeleteOp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("vcp-performance", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("vcp-performance")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
nodeSelectorList []*NodeSelector
|
|
||||||
policyName string
|
|
||||||
datastoreName string
|
|
||||||
volumeCount int
|
|
||||||
volumesPerPod int
|
|
||||||
iterations int
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
|
|
||||||
// Read the environment variables
|
|
||||||
volumeCount = GetAndExpectIntEnvVar(VCPPerfVolumeCount)
|
|
||||||
volumesPerPod = GetAndExpectIntEnvVar(VCPPerfVolumesPerPod)
|
|
||||||
iterations = GetAndExpectIntEnvVar(VCPPerfIterations)
|
|
||||||
|
|
||||||
policyName = GetAndExpectStringEnvVar(SPBMPolicyName)
|
|
||||||
datastoreName = GetAndExpectStringEnvVar(StorageClassDatastoreName)
|
|
||||||
|
|
||||||
nodes, err := e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
gomega.Expect(nodes.Items).ToNot(gomega.BeEmpty(), "Requires at least one ready node")
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("Cannot attach %d volumes to %d nodes. Maximum volumes that can be attached on %d nodes is %d", volumeCount, len(nodes.Items), len(nodes.Items), SCSIUnitsAvailablePerNode*len(nodes.Items))
|
|
||||||
gomega.Expect(volumeCount).To(gomega.BeNumerically("<=", SCSIUnitsAvailablePerNode*len(nodes.Items)), msg)
|
|
||||||
|
|
||||||
msg = fmt.Sprintf("Cannot attach %d volumes per pod. Maximum volumes that can be attached per pod is %d", volumesPerPod, SCSIUnitsAvailablePerNode)
|
|
||||||
gomega.Expect(volumesPerPod).To(gomega.BeNumerically("<=", SCSIUnitsAvailablePerNode), msg)
|
|
||||||
|
|
||||||
nodeSelectorList = createNodeLabels(client, namespace, nodes)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("vcp performance tests", func(ctx context.Context) {
|
|
||||||
scList := getTestStorageClasses(ctx, client, policyName, datastoreName)
|
|
||||||
for _, sc := range scList {
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), sc.Name, metav1.DeleteOptions{})
|
|
||||||
}
|
|
||||||
|
|
||||||
sumLatency := make(map[string]float64)
|
|
||||||
for i := 0; i < iterations; i++ {
|
|
||||||
latency := invokeVolumeLifeCyclePerformance(ctx, f, client, namespace, scList, volumesPerPod, volumeCount, nodeSelectorList)
|
|
||||||
for key, val := range latency {
|
|
||||||
sumLatency[key] += val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iterations64 := float64(iterations)
|
|
||||||
framework.Logf("Average latency for below operations")
|
|
||||||
framework.Logf("Creating %d PVCs and waiting for bound phase: %v seconds", volumeCount, sumLatency[CreateOp]/iterations64)
|
|
||||||
framework.Logf("Creating %v Pod: %v seconds", volumeCount/volumesPerPod, sumLatency[AttachOp]/iterations64)
|
|
||||||
framework.Logf("Deleting %v Pod and waiting for disk to be detached: %v seconds", volumeCount/volumesPerPod, sumLatency[DetachOp]/iterations64)
|
|
||||||
framework.Logf("Deleting %v PVCs: %v seconds", volumeCount, sumLatency[DeleteOp]/iterations64)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func getTestStorageClasses(ctx context.Context, client clientset.Interface, policyName, datastoreName string) []*storagev1.StorageClass {
|
|
||||||
const (
|
|
||||||
storageclass1 = "sc-default"
|
|
||||||
storageclass2 = "sc-vsan"
|
|
||||||
storageclass3 = "sc-spbm"
|
|
||||||
storageclass4 = "sc-user-specified-ds"
|
|
||||||
)
|
|
||||||
scNames := []string{storageclass1, storageclass2, storageclass3, storageclass4}
|
|
||||||
scArrays := make([]*storagev1.StorageClass, len(scNames))
|
|
||||||
for index, scname := range scNames {
|
|
||||||
// Create vSphere Storage Class
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating Storage Class : %v", scname))
|
|
||||||
var sc *storagev1.StorageClass
|
|
||||||
var err error
|
|
||||||
switch scname {
|
|
||||||
case storageclass1:
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass1, nil, nil, ""), metav1.CreateOptions{})
|
|
||||||
case storageclass2:
|
|
||||||
var scVSanParameters map[string]string
|
|
||||||
scVSanParameters = make(map[string]string)
|
|
||||||
scVSanParameters[PolicyHostFailuresToTolerate] = "1"
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass2, scVSanParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
case storageclass3:
|
|
||||||
var scSPBMPolicyParameters map[string]string
|
|
||||||
scSPBMPolicyParameters = make(map[string]string)
|
|
||||||
scSPBMPolicyParameters[SpbmStoragePolicy] = policyName
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec(storageclass3, scSPBMPolicyParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
case storageclass4:
|
|
||||||
var scWithDSParameters map[string]string
|
|
||||||
scWithDSParameters = make(map[string]string)
|
|
||||||
scWithDSParameters[Datastore] = datastoreName
|
|
||||||
scWithDatastoreSpec := getVSphereStorageClassSpec(storageclass4, scWithDSParameters, nil, "")
|
|
||||||
sc, err = client.StorageV1().StorageClasses().Create(ctx, scWithDatastoreSpec, metav1.CreateOptions{})
|
|
||||||
}
|
|
||||||
gomega.Expect(sc).NotTo(gomega.BeNil())
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
scArrays[index] = sc
|
|
||||||
}
|
|
||||||
return scArrays
|
|
||||||
}
|
|
||||||
|
|
||||||
// invokeVolumeLifeCyclePerformance peforms full volume life cycle management and records latency for each operation
|
|
||||||
func invokeVolumeLifeCyclePerformance(ctx context.Context, f *framework.Framework, client clientset.Interface, namespace string, sc []*storagev1.StorageClass, volumesPerPod int, volumeCount int, nodeSelectorList []*NodeSelector) (latency map[string]float64) {
|
|
||||||
var (
|
|
||||||
totalpvclaims [][]*v1.PersistentVolumeClaim
|
|
||||||
totalpvs [][]*v1.PersistentVolume
|
|
||||||
totalpods []*v1.Pod
|
|
||||||
)
|
|
||||||
nodeVolumeMap := make(map[string][]string)
|
|
||||||
latency = make(map[string]float64)
|
|
||||||
numPods := volumeCount / volumesPerPod
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating %d PVCs", volumeCount))
|
|
||||||
start := time.Now()
|
|
||||||
for i := 0; i < numPods; i++ {
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
for j := 0; j < volumesPerPod; j++ {
|
|
||||||
currsc := sc[((i*numPods)+j)%len(sc)]
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", currsc))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
}
|
|
||||||
totalpvclaims = append(totalpvclaims, pvclaims)
|
|
||||||
}
|
|
||||||
for _, pvclaims := range totalpvclaims {
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
totalpvs = append(totalpvs, persistentvolumes)
|
|
||||||
}
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
latency[CreateOp] = elapsed.Seconds()
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod to attach PVs to the node")
|
|
||||||
start = time.Now()
|
|
||||||
for i, pvclaims := range totalpvclaims {
|
|
||||||
nodeSelector := nodeSelectorList[i%len(nodeSelectorList)]
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, map[string]string{nodeSelector.labelKey: nodeSelector.labelValue}, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
totalpods = append(totalpods, pod)
|
|
||||||
|
|
||||||
ginkgo.DeferCleanup(e2epod.DeletePodWithWait, client, pod)
|
|
||||||
}
|
|
||||||
elapsed = time.Since(start)
|
|
||||||
latency[AttachOp] = elapsed.Seconds()
|
|
||||||
|
|
||||||
for i, pod := range totalpods {
|
|
||||||
verifyVSphereVolumesAccessible(ctx, client, pod, totalpvs[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Deleting pods")
|
|
||||||
start = time.Now()
|
|
||||||
for _, pod := range totalpods {
|
|
||||||
err := e2epod.DeletePodWithWait(ctx, client, pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
elapsed = time.Since(start)
|
|
||||||
latency[DetachOp] = elapsed.Seconds()
|
|
||||||
|
|
||||||
for i, pod := range totalpods {
|
|
||||||
for _, pv := range totalpvs[i] {
|
|
||||||
nodeVolumeMap[pod.Spec.NodeName] = append(nodeVolumeMap[pod.Spec.NodeName], pv.Spec.VsphereVolume.VolumePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := waitForVSphereDisksToDetach(ctx, nodeVolumeMap)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting the PVCs")
|
|
||||||
start = time.Now()
|
|
||||||
for _, pvclaims := range totalpvclaims {
|
|
||||||
for _, pvc := range pvclaims {
|
|
||||||
err = e2epv.DeletePersistentVolumeClaim(ctx, client, pvc.Name, namespace)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elapsed = time.Since(start)
|
|
||||||
latency[DeleteOp] = elapsed.Seconds()
|
|
||||||
|
|
||||||
return latency
|
|
||||||
}
|
|
||||||
@@ -1,391 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Volume Placement", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-placement")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
const (
|
|
||||||
NodeLabelKey = "vsphere_e2e_label_volume_placement"
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
c clientset.Interface
|
|
||||||
ns string
|
|
||||||
volumePaths []string
|
|
||||||
node1Name string
|
|
||||||
node1KeyValueLabel map[string]string
|
|
||||||
node2Name string
|
|
||||||
node2KeyValueLabel map[string]string
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
vsp *VSphere
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
c = f.ClientSet
|
|
||||||
ns = f.Namespace.Name
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, c, f.Timeouts.NodeSchedulable))
|
|
||||||
node1Name, node1KeyValueLabel, node2Name, node2KeyValueLabel = testSetupVolumePlacement(ctx, c, ns)
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
if len(node1KeyValueLabel) > 0 {
|
|
||||||
e2enode.RemoveLabelOffNode(c, node1Name, NodeLabelKey)
|
|
||||||
}
|
|
||||||
if len(node2KeyValueLabel) > 0 {
|
|
||||||
e2enode.RemoveLabelOffNode(c, node2Name, NodeLabelKey)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
nodeInfo = TestContext.NodeMapper.GetNodeInfo(node1Name)
|
|
||||||
vsp = nodeInfo.VSphere
|
|
||||||
ginkgo.By("creating vmdk")
|
|
||||||
volumePath, err := vsp.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePaths = append(volumePaths, volumePath)
|
|
||||||
ginkgo.DeferCleanup(func() {
|
|
||||||
for _, volumePath := range volumePaths {
|
|
||||||
vsp.DeleteVolume(volumePath, nodeInfo.DataCenterRef)
|
|
||||||
}
|
|
||||||
volumePaths = nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Steps
|
|
||||||
|
|
||||||
1. Create pod Spec with volume path of the vmdk and NodeSelector set to label assigned to node1.
|
|
||||||
2. Create pod and wait for pod to become ready.
|
|
||||||
3. Verify volume is attached to the node1.
|
|
||||||
4. Create empty file on the volume to verify volume is writable.
|
|
||||||
5. Verify newly created file and previously created files exist on the volume.
|
|
||||||
6. Delete pod.
|
|
||||||
7. Wait for volume to be detached from the node1.
|
|
||||||
8. Repeat Step 1 to 7 and make sure back to back pod creation on same worker node with the same volume is working as expected.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
ginkgo.It("should create and delete pod with the same volume source on the same worker node", func(ctx context.Context) {
|
|
||||||
var volumeFiles []string
|
|
||||||
pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
newEmptyFileName := fmt.Sprintf("/mnt/volume1/%v_1.txt", ns)
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFileName)
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the same node: %v", node1Name))
|
|
||||||
pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
newEmptyFileName = fmt.Sprintf("/mnt/volume1/%v_2.txt", ns)
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFileName)
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Steps
|
|
||||||
|
|
||||||
1. Create pod Spec with volume path of the vmdk1 and NodeSelector set to node1's label.
|
|
||||||
2. Create pod and wait for POD to become ready.
|
|
||||||
3. Verify volume is attached to the node1.
|
|
||||||
4. Create empty file on the volume to verify volume is writable.
|
|
||||||
5. Verify newly created file and previously created files exist on the volume.
|
|
||||||
6. Delete pod.
|
|
||||||
7. Wait for volume to be detached from the node1.
|
|
||||||
8. Create pod Spec with volume path of the vmdk1 and NodeSelector set to node2's label.
|
|
||||||
9. Create pod and wait for pod to become ready.
|
|
||||||
10. Verify volume is attached to the node2.
|
|
||||||
11. Create empty file on the volume to verify volume is writable.
|
|
||||||
12. Verify newly created file and previously created files exist on the volume.
|
|
||||||
13. Delete pod.
|
|
||||||
*/
|
|
||||||
|
|
||||||
ginkgo.It("should create and delete pod with the same volume source attach/detach to different worker nodes", func(ctx context.Context) {
|
|
||||||
var volumeFiles []string
|
|
||||||
pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
newEmptyFileName := fmt.Sprintf("/mnt/volume1/%v_1.txt", ns)
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFileName)
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the another node: %v", node2Name))
|
|
||||||
pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node2Name, node2KeyValueLabel, volumePaths)
|
|
||||||
|
|
||||||
newEmptyFileName = fmt.Sprintf("/mnt/volume1/%v_2.txt", ns)
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFileName)
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, []string{newEmptyFileName}, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node2Name, volumePaths)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test multiple volumes from same datastore within the same pod
|
|
||||||
1. Create volumes - vmdk2
|
|
||||||
2. Create pod Spec with volume path of vmdk1 (vmdk1 is created in test setup) and vmdk2.
|
|
||||||
3. Create pod using spec created in step-2 and wait for pod to become ready.
|
|
||||||
4. Verify both volumes are attached to the node on which pod are created. Write some data to make sure volume are accessible.
|
|
||||||
5. Delete pod.
|
|
||||||
6. Wait for vmdk1 and vmdk2 to be detached from node.
|
|
||||||
7. Create pod using spec created in step-2 and wait for pod to become ready.
|
|
||||||
8. Verify both volumes are attached to the node on which PODs are created. Verify volume contents are matching with the content written in step 4.
|
|
||||||
9. Delete POD.
|
|
||||||
10. Wait for vmdk1 and vmdk2 to be detached from node.
|
|
||||||
*/
|
|
||||||
|
|
||||||
ginkgo.It("should create and delete pod with multiple volumes from same datastore", func(ctx context.Context) {
|
|
||||||
ginkgo.By("creating another vmdk")
|
|
||||||
volumePath, err := vsp.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePaths = append(volumePaths, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume: %v and volume: %v", node1Name, volumePaths[0], volumePaths[1]))
|
|
||||||
pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
volumeFiles := []string{
|
|
||||||
fmt.Sprintf("/mnt/volume1/%v_1.txt", ns),
|
|
||||||
fmt.Sprintf("/mnt/volume2/%v_1.txt", ns),
|
|
||||||
}
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, volumeFiles, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths)
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume :%v and volume: %v", node1Name, volumePaths[0], volumePaths[1]))
|
|
||||||
pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
newEmptyFilesNames := []string{
|
|
||||||
fmt.Sprintf("/mnt/volume1/%v_2.txt", ns),
|
|
||||||
fmt.Sprintf("/mnt/volume2/%v_2.txt", ns),
|
|
||||||
}
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFilesNames[0])
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFilesNames[1])
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, newEmptyFilesNames, volumeFiles)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test multiple volumes from different datastore within the same pod
|
|
||||||
1. Create volumes - vmdk2 on non default shared datastore.
|
|
||||||
2. Create pod Spec with volume path of vmdk1 (vmdk1 is created in test setup on default datastore) and vmdk2.
|
|
||||||
3. Create pod using spec created in step-2 and wait for pod to become ready.
|
|
||||||
4. Verify both volumes are attached to the node on which pod are created. Write some data to make sure volume are accessible.
|
|
||||||
5. Delete pod.
|
|
||||||
6. Wait for vmdk1 and vmdk2 to be detached from node.
|
|
||||||
7. Create pod using spec created in step-2 and wait for pod to become ready.
|
|
||||||
8. Verify both volumes are attached to the node on which PODs are created. Verify volume contents are matching with the content written in step 4.
|
|
||||||
9. Delete POD.
|
|
||||||
10. Wait for vmdk1 and vmdk2 to be detached from node.
|
|
||||||
*/
|
|
||||||
ginkgo.It("should create and delete pod with multiple volumes from different datastore", func(ctx context.Context) {
|
|
||||||
ginkgo.By("creating another vmdk on non default shared datastore")
|
|
||||||
var volumeOptions *VolumeOptions
|
|
||||||
volumeOptions = new(VolumeOptions)
|
|
||||||
volumeOptions.CapacityKB = 2097152
|
|
||||||
volumeOptions.Name = "e2e-vmdk-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
|
||||||
volumeOptions.Datastore = GetAndExpectStringEnvVar(SecondSharedDatastore)
|
|
||||||
volumePath, err := vsp.CreateVolume(volumeOptions, nodeInfo.DataCenterRef)
|
|
||||||
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePaths = append(volumePaths, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume :%v and volume: %v", node1Name, volumePaths[0], volumePaths[1]))
|
|
||||||
pod := createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
volumeFiles := []string{
|
|
||||||
fmt.Sprintf("/mnt/volume1/%v_1.txt", ns),
|
|
||||||
fmt.Sprintf("/mnt/volume2/%v_1.txt", ns),
|
|
||||||
}
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, volumeFiles, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the node: %v with volume :%v and volume: %v", node1Name, volumePaths[0], volumePaths[1]))
|
|
||||||
pod = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, volumePaths)
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
newEmptyFileNames := []string{
|
|
||||||
fmt.Sprintf("/mnt/volume1/%v_2.txt", ns),
|
|
||||||
fmt.Sprintf("/mnt/volume2/%v_2.txt", ns),
|
|
||||||
}
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFileNames[0])
|
|
||||||
volumeFiles = append(volumeFiles, newEmptyFileNames[1])
|
|
||||||
createAndVerifyFilesOnVolume(ns, pod.Name, newEmptyFileNames, volumeFiles)
|
|
||||||
deletePodAndWaitForVolumeToDetach(ctx, f, c, pod, node1Name, volumePaths)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test Back-to-back pod creation/deletion with different volume sources on the same worker node
|
|
||||||
1. Create volumes - vmdk2
|
|
||||||
2. Create pod Spec - pod-SpecA with volume path of vmdk1 and NodeSelector set to label assigned to node1.
|
|
||||||
3. Create pod Spec - pod-SpecB with volume path of vmdk2 and NodeSelector set to label assigned to node1.
|
|
||||||
4. Create pod-A using pod-SpecA and wait for pod to become ready.
|
|
||||||
5. Create pod-B using pod-SpecB and wait for POD to become ready.
|
|
||||||
6. Verify volumes are attached to the node.
|
|
||||||
7. Create empty file on the volume to make sure volume is accessible. (Perform this step on pod-A and pod-B)
|
|
||||||
8. Verify file created in step 5 is present on the volume. (perform this step on pod-A and pod-B)
|
|
||||||
9. Delete pod-A and pod-B
|
|
||||||
10. Repeatedly (5 times) perform step 4 to 9 and verify associated volume's content is matching.
|
|
||||||
11. Wait for vmdk1 and vmdk2 to be detached from node.
|
|
||||||
*/
|
|
||||||
ginkgo.It("test back to back pod creation and deletion with different volume sources on the same worker node", func(ctx context.Context) {
|
|
||||||
var (
|
|
||||||
podA *v1.Pod
|
|
||||||
podB *v1.Pod
|
|
||||||
testvolumePathsPodA []string
|
|
||||||
testvolumePathsPodB []string
|
|
||||||
podAFiles []string
|
|
||||||
podBFiles []string
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
ginkgo.By("clean up undeleted pods")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podA), "defer: Failed to delete pod ", podA.Name)
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podB), "defer: Failed to delete pod ", podB.Name)
|
|
||||||
ginkgo.By(fmt.Sprintf("wait for volumes to be detached from the node: %v", node1Name))
|
|
||||||
for _, volumePath := range volumePaths {
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, volumePath, node1Name))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
testvolumePathsPodA = append(testvolumePathsPodA, volumePaths[0])
|
|
||||||
// Create another VMDK Volume
|
|
||||||
ginkgo.By("creating another vmdk")
|
|
||||||
volumePath, err := vsp.CreateVolume(&VolumeOptions{}, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePaths = append(volumePaths, volumePath)
|
|
||||||
testvolumePathsPodB = append(testvolumePathsPodA, volumePath)
|
|
||||||
|
|
||||||
for index := 0; index < 5; index++ {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod-A on the node: %v with volume: %v", node1Name, testvolumePathsPodA[0]))
|
|
||||||
podA = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, testvolumePathsPodA)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod-B on the node: %v with volume: %v", node1Name, testvolumePathsPodB[0]))
|
|
||||||
podB = createPodWithVolumeAndNodeSelector(ctx, c, ns, node1Name, node1KeyValueLabel, testvolumePathsPodB)
|
|
||||||
|
|
||||||
podAFileName := fmt.Sprintf("/mnt/volume1/podA_%v_%v.txt", ns, index+1)
|
|
||||||
podBFileName := fmt.Sprintf("/mnt/volume1/podB_%v_%v.txt", ns, index+1)
|
|
||||||
podAFiles = append(podAFiles, podAFileName)
|
|
||||||
podBFiles = append(podBFiles, podBFileName)
|
|
||||||
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
ginkgo.By("Creating empty file on volume mounted on pod-A")
|
|
||||||
e2eoutput.CreateEmptyFileOnPod(ns, podA.Name, podAFileName)
|
|
||||||
|
|
||||||
ginkgo.By("Creating empty file volume mounted on pod-B")
|
|
||||||
e2eoutput.CreateEmptyFileOnPod(ns, podB.Name, podBFileName)
|
|
||||||
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
ginkgo.By("Verify newly Created file and previously created files present on volume mounted on pod-A")
|
|
||||||
verifyFilesExistOnVSphereVolume(ns, podA.Name, podAFiles...)
|
|
||||||
ginkgo.By("Verify newly Created file and previously created files present on volume mounted on pod-B")
|
|
||||||
verifyFilesExistOnVSphereVolume(ns, podB.Name, podBFiles...)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting pod-A")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podA), "Failed to delete pod ", podA.Name)
|
|
||||||
ginkgo.By("Deleting pod-B")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, podB), "Failed to delete pod ", podB.Name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func testSetupVolumePlacement(ctx context.Context, client clientset.Interface, namespace string) (node1Name string, node1KeyValueLabel map[string]string, node2Name string, node2KeyValueLabel map[string]string) {
|
|
||||||
nodes, err := e2enode.GetBoundedReadySchedulableNodes(ctx, client, 2)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if len(nodes.Items) < 2 {
|
|
||||||
e2eskipper.Skipf("Requires at least %d nodes (not %d)", 2, len(nodes.Items))
|
|
||||||
}
|
|
||||||
node1Name = nodes.Items[0].Name
|
|
||||||
node2Name = nodes.Items[1].Name
|
|
||||||
node1LabelValue := "vsphere_e2e_" + string(uuid.NewUUID())
|
|
||||||
node1KeyValueLabel = make(map[string]string)
|
|
||||||
node1KeyValueLabel[NodeLabelKey] = node1LabelValue
|
|
||||||
e2enode.AddOrUpdateLabelOnNode(client, node1Name, NodeLabelKey, node1LabelValue)
|
|
||||||
|
|
||||||
node2LabelValue := "vsphere_e2e_" + string(uuid.NewUUID())
|
|
||||||
node2KeyValueLabel = make(map[string]string)
|
|
||||||
node2KeyValueLabel[NodeLabelKey] = node2LabelValue
|
|
||||||
e2enode.AddOrUpdateLabelOnNode(client, node2Name, NodeLabelKey, node2LabelValue)
|
|
||||||
return node1Name, node1KeyValueLabel, node2Name, node2KeyValueLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPodWithVolumeAndNodeSelector(ctx context.Context, client clientset.Interface, namespace string, nodeName string, nodeKeyValueLabel map[string]string, volumePaths []string) *v1.Pod {
|
|
||||||
var pod *v1.Pod
|
|
||||||
var err error
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod on the node: %v", nodeName))
|
|
||||||
podspec := getVSpherePodSpecWithVolumePaths(volumePaths, nodeKeyValueLabel, nil)
|
|
||||||
|
|
||||||
pod, err = client.CoreV1().Pods(namespace).Create(ctx, podspec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.By("Waiting for pod to be ready")
|
|
||||||
gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed())
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Verify volume is attached to the node:%v", nodeName))
|
|
||||||
for _, volumePath := range volumePaths {
|
|
||||||
isAttached, err := diskIsAttached(ctx, volumePath, nodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if !isAttached {
|
|
||||||
framework.Failf("Volume: %s is not attached to the node: %v", volumePath, nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pod
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAndVerifyFilesOnVolume(namespace string, podname string, newEmptyfilesToCreate []string, filesToCheck []string) {
|
|
||||||
// Create empty files on the mounted volumes on the pod to verify volume is writable
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating empty file on volume mounted on: %v", podname))
|
|
||||||
createEmptyFilesOnVSphereVolume(namespace, podname, newEmptyfilesToCreate)
|
|
||||||
|
|
||||||
// Verify newly and previously created files present on the volume mounted on the pod
|
|
||||||
ginkgo.By(fmt.Sprintf("Verify newly Created file and previously created files present on volume mounted on: %v", podname))
|
|
||||||
verifyFilesExistOnVSphereVolume(namespace, podname, filesToCheck...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deletePodAndWaitForVolumeToDetach(ctx context.Context, f *framework.Framework, c clientset.Interface, pod *v1.Pod, nodeName string, volumePaths []string) {
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, pod), "Failed to delete pod ", pod.Name)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for volume to be detached from the node")
|
|
||||||
for _, volumePath := range volumePaths {
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, volumePath, nodeName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify that a volume remains attached through vpxd restart.
|
|
||||||
|
|
||||||
For the number of schedulable nodes:
|
|
||||||
1. Create a Volume with default options.
|
|
||||||
2. Create a Pod with the created Volume.
|
|
||||||
3. Verify that the Volume is attached.
|
|
||||||
4. Create a file with random contents under the Volume's mount point on the Pod.
|
|
||||||
5. Stop the vpxd service on the vCenter host.
|
|
||||||
6. Verify that the file is accessible on the Pod and that it's contents match.
|
|
||||||
7. Start the vpxd service on the vCenter host.
|
|
||||||
8. Verify that the Volume remains attached, the file is accessible on the Pod, and that it's contents match.
|
|
||||||
9. Delete the Pod and wait for the Volume to be detached.
|
|
||||||
10. Delete the Volume.
|
|
||||||
*/
|
|
||||||
var _ = utils.SIGDescribe("Verify Volume Attach Through vpxd Restart", feature.Vsphere, framework.WithSerial(), framework.WithDisruptive(), func() {
|
|
||||||
f := framework.NewDefaultFramework("restart-vpxd")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
|
|
||||||
type node struct {
|
|
||||||
name string
|
|
||||||
kvLabels map[string]string
|
|
||||||
nodeInfo *NodeInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
labelKey = "vsphere_e2e_label_vpxd_restart"
|
|
||||||
vpxdServiceName = "vmware-vpxd"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
vcNodesMap map[string][]node
|
|
||||||
)
|
|
||||||
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
// Requires SSH access to vCenter.
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, client, f.Timeouts.NodeSchedulable))
|
|
||||||
|
|
||||||
nodes, err := e2enode.GetReadySchedulableNodes(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
numNodes := len(nodes.Items)
|
|
||||||
|
|
||||||
vcNodesMap = make(map[string][]node)
|
|
||||||
for i := 0; i < numNodes; i++ {
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(nodes.Items[i].Name)
|
|
||||||
nodeName := nodes.Items[i].Name
|
|
||||||
nodeLabel := "vsphere_e2e_" + string(uuid.NewUUID())
|
|
||||||
e2enode.AddOrUpdateLabelOnNode(client, nodeName, labelKey, nodeLabel)
|
|
||||||
|
|
||||||
vcHost := nodeInfo.VSphere.Config.Hostname
|
|
||||||
vcNodesMap[vcHost] = append(vcNodesMap[vcHost], node{
|
|
||||||
name: nodeName,
|
|
||||||
kvLabels: map[string]string{labelKey: nodeLabel},
|
|
||||||
nodeInfo: nodeInfo,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify volume remains attached through vpxd restart", func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessSSHKeyPresent()
|
|
||||||
|
|
||||||
for vcHost, nodes := range vcNodesMap {
|
|
||||||
var (
|
|
||||||
volumePaths []string
|
|
||||||
filePaths []string
|
|
||||||
fileContents []string
|
|
||||||
pods []*v1.Pod
|
|
||||||
)
|
|
||||||
|
|
||||||
framework.Logf("Testing for nodes on vCenter host: %s", vcHost)
|
|
||||||
|
|
||||||
for i, node := range nodes {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating test vsphere volume %d", i))
|
|
||||||
volumePath, err := node.nodeInfo.VSphere.CreateVolume(&VolumeOptions{}, node.nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
volumePaths = append(volumePaths, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating pod %d on node %v", i, node.name))
|
|
||||||
podspec := getVSpherePodSpecWithVolumePaths([]string{volumePath}, node.kvLabels, nil)
|
|
||||||
pod, err := client.CoreV1().Pods(namespace).Create(ctx, podspec, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Waiting for pod %d to be ready", i))
|
|
||||||
gomega.Expect(e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, namespace)).To(gomega.Succeed())
|
|
||||||
|
|
||||||
pod, err = client.CoreV1().Pods(namespace).Get(ctx, pod.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
pods = append(pods, pod)
|
|
||||||
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
ginkgo.By(fmt.Sprintf("Verifying that volume %v is attached to node %v", volumePath, nodeName))
|
|
||||||
expectVolumeToBeAttached(ctx, nodeName, volumePath)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating a file with random content on the volume mounted on pod %d", i))
|
|
||||||
filePath := fmt.Sprintf("/mnt/volume1/%v_vpxd_restart_test_%v.txt", namespace, strconv.FormatInt(time.Now().UnixNano(), 10))
|
|
||||||
randomContent := fmt.Sprintf("Random Content -- %v", strconv.FormatInt(time.Now().UnixNano(), 10))
|
|
||||||
err = writeContentToPodFile(namespace, pod.Name, filePath, randomContent)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
filePaths = append(filePaths, filePath)
|
|
||||||
fileContents = append(fileContents, randomContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Stopping vpxd on the vCenter host")
|
|
||||||
vcAddress := vcHost + ":22"
|
|
||||||
err := invokeVCenterServiceControl(ctx, "stop", vpxdServiceName, vcAddress)
|
|
||||||
framework.ExpectNoError(err, "Unable to stop vpxd on the vCenter host")
|
|
||||||
|
|
||||||
expectFilesToBeAccessible(namespace, pods, filePaths)
|
|
||||||
expectFileContentsToMatch(namespace, pods, filePaths, fileContents)
|
|
||||||
|
|
||||||
ginkgo.By("Starting vpxd on the vCenter host")
|
|
||||||
err = invokeVCenterServiceControl(ctx, "start", vpxdServiceName, vcAddress)
|
|
||||||
framework.ExpectNoError(err, "Unable to start vpxd on the vCenter host")
|
|
||||||
|
|
||||||
expectVolumesToBeAttached(ctx, pods, volumePaths)
|
|
||||||
expectFilesToBeAccessible(namespace, pods, filePaths)
|
|
||||||
expectFileContentsToMatch(namespace, pods, filePaths, fileContents)
|
|
||||||
|
|
||||||
for i, node := range nodes {
|
|
||||||
pod := pods[i]
|
|
||||||
nodeName := pod.Spec.NodeName
|
|
||||||
volumePath := volumePaths[i]
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Deleting pod on node %s", nodeName))
|
|
||||||
err = e2epod.DeletePodWithWait(ctx, client, pod)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Waiting for volume %s to be detached from node %s", volumePath, nodeName))
|
|
||||||
err = waitForVSphereDiskToDetach(ctx, volumePath, nodeName)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By(fmt.Sprintf("Deleting volume %s", volumePath))
|
|
||||||
err = node.nodeInfo.VSphere.DeleteVolume(volumePath, node.nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"hash/fnv"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
vmfsDatastore = "sharedVmfs-0"
|
|
||||||
vsanDatastore = "vsanDatastore"
|
|
||||||
dummyVMPrefixName = "vsphere-k8s"
|
|
||||||
diskStripesCapabilityMaxVal = "11"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify the storage policy based management for dynamic volume provisioning inside kubernetes.
|
|
||||||
There are 2 ways to achieve it:
|
|
||||||
1. Specify VSAN storage capabilities in the storage-class.
|
|
||||||
2. Use existing vCenter SPBM storage policies.
|
|
||||||
|
|
||||||
Valid VSAN storage capabilities are mentioned below:
|
|
||||||
1. hostFailuresToTolerate
|
|
||||||
2. forceProvisioning
|
|
||||||
3. cacheReservation
|
|
||||||
4. diskStripes
|
|
||||||
5. objectSpaceReservation
|
|
||||||
6. iopsLimit
|
|
||||||
|
|
||||||
Steps
|
|
||||||
1. Create StorageClass with.
|
|
||||||
a. VSAN storage capabilities set to valid/invalid values (or)
|
|
||||||
b. Use existing vCenter SPBM storage policies.
|
|
||||||
2. Create PVC which uses the StorageClass created in step 1.
|
|
||||||
3. Wait for PV to be provisioned.
|
|
||||||
4. Wait for PVC's status to become Bound
|
|
||||||
5. Create pod using PVC on specific node.
|
|
||||||
6. Wait for Disk to be attached to the node.
|
|
||||||
7. Delete pod and Wait for Volume Disk to be detached from the Node.
|
|
||||||
8. Delete PVC, PV and Storage Class
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Storage Policy Based Volume Provisioning", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("volume-vsan-policy")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
client clientset.Interface
|
|
||||||
namespace string
|
|
||||||
scParameters map[string]string
|
|
||||||
policyName string
|
|
||||||
tagPolicy string
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
client = f.ClientSet
|
|
||||||
namespace = f.Namespace.Name
|
|
||||||
policyName = GetAndExpectStringEnvVar(SPBMPolicyName)
|
|
||||||
tagPolicy = GetAndExpectStringEnvVar(SPBMTagPolicy)
|
|
||||||
framework.Logf("framework: %+v", f)
|
|
||||||
scParameters = make(map[string]string)
|
|
||||||
_, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Valid policy.
|
|
||||||
ginkgo.It("verify VSAN storage capability with valid hostFailuresToTolerate and cacheReservation values is honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy hostFailuresToTolerate: %s, cacheReservation: %s", HostFailuresToTolerateCapabilityVal, CacheReservationCapabilityVal))
|
|
||||||
scParameters[PolicyHostFailuresToTolerate] = HostFailuresToTolerateCapabilityVal
|
|
||||||
scParameters[PolicyCacheReservation] = CacheReservationCapabilityVal
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Valid policy.
|
|
||||||
ginkgo.It("verify VSAN storage capability with valid diskStripes and objectSpaceReservation values is honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy diskStripes: %s, objectSpaceReservation: %s", DiskStripesCapabilityVal, ObjectSpaceReservationCapabilityVal))
|
|
||||||
scParameters[PolicyDiskStripes] = "1"
|
|
||||||
scParameters[PolicyObjectSpaceReservation] = "30"
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Valid policy.
|
|
||||||
ginkgo.It("verify VSAN storage capability with valid diskStripes and objectSpaceReservation values and a VSAN datastore is honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy diskStripes: %s, objectSpaceReservation: %s", DiskStripesCapabilityVal, ObjectSpaceReservationCapabilityVal))
|
|
||||||
scParameters[PolicyDiskStripes] = DiskStripesCapabilityVal
|
|
||||||
scParameters[PolicyObjectSpaceReservation] = ObjectSpaceReservationCapabilityVal
|
|
||||||
scParameters[Datastore] = vsanDatastore
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Valid policy.
|
|
||||||
ginkgo.It("verify VSAN storage capability with valid objectSpaceReservation and iopsLimit values is honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy objectSpaceReservation: %s, iopsLimit: %s", ObjectSpaceReservationCapabilityVal, IopsLimitCapabilityVal))
|
|
||||||
scParameters[PolicyObjectSpaceReservation] = ObjectSpaceReservationCapabilityVal
|
|
||||||
scParameters[PolicyIopsLimit] = IopsLimitCapabilityVal
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Invalid VSAN storage capabilities parameters.
|
|
||||||
ginkgo.It("verify VSAN storage capability with invalid capability name objectSpaceReserve is not honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy objectSpaceReserve: %s, stripeWidth: %s", ObjectSpaceReservationCapabilityVal, StripeWidthCapabilityVal))
|
|
||||||
scParameters["objectSpaceReserve"] = ObjectSpaceReservationCapabilityVal
|
|
||||||
scParameters[PolicyDiskStripes] = StripeWidthCapabilityVal
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "invalid option \\\"objectSpaceReserve\\\" for volume plugin kubernetes.io/vsphere-volume"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Invalid policy on a VSAN test bed.
|
|
||||||
// diskStripes value has to be between 1 and 12.
|
|
||||||
ginkgo.It("verify VSAN storage capability with invalid diskStripes value is not honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy diskStripes: %s, cacheReservation: %s", DiskStripesCapabilityInvalidVal, CacheReservationCapabilityVal))
|
|
||||||
scParameters[PolicyDiskStripes] = DiskStripesCapabilityInvalidVal
|
|
||||||
scParameters[PolicyCacheReservation] = CacheReservationCapabilityVal
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "Invalid value for " + PolicyDiskStripes + "."
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Invalid policy on a VSAN test bed.
|
|
||||||
// hostFailuresToTolerate value has to be between 0 and 3 including.
|
|
||||||
ginkgo.It("verify VSAN storage capability with invalid hostFailuresToTolerate value is not honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy hostFailuresToTolerate: %s", HostFailuresToTolerateCapabilityInvalidVal))
|
|
||||||
scParameters[PolicyHostFailuresToTolerate] = HostFailuresToTolerateCapabilityInvalidVal
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "Invalid value for " + PolicyHostFailuresToTolerate + "."
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Specify a valid VSAN policy on a non-VSAN test bed.
|
|
||||||
// The test should fail.
|
|
||||||
ginkgo.It("verify VSAN storage capability with non-vsan datastore is not honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for VSAN policy diskStripes: %s, objectSpaceReservation: %s and a non-VSAN datastore: %s", DiskStripesCapabilityVal, ObjectSpaceReservationCapabilityVal, vmfsDatastore))
|
|
||||||
scParameters[PolicyDiskStripes] = DiskStripesCapabilityVal
|
|
||||||
scParameters[PolicyObjectSpaceReservation] = ObjectSpaceReservationCapabilityVal
|
|
||||||
scParameters[Datastore] = vmfsDatastore
|
|
||||||
framework.Logf("Invoking test for VSAN storage capabilities: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "The specified datastore: \\\"" + vmfsDatastore + "\\\" is not a VSAN datastore. " +
|
|
||||||
"The policy parameters will work only with VSAN Datastore."
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify an existing and compatible SPBM policy is honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for SPBM policy: %s", policyName))
|
|
||||||
scParameters[SpbmStoragePolicy] = policyName
|
|
||||||
scParameters[DiskFormat] = ThinDisk
|
|
||||||
framework.Logf("Invoking test for SPBM storage policy: %+v", scParameters)
|
|
||||||
invokeValidPolicyTest(ctx, f, client, namespace, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify clean up of stale dummy VM for dynamically provisioned pvc using SPBM policy", func(ctx context.Context) {
|
|
||||||
scParameters[PolicyDiskStripes] = diskStripesCapabilityMaxVal
|
|
||||||
scParameters[PolicyObjectSpaceReservation] = ObjectSpaceReservationCapabilityVal
|
|
||||||
scParameters[Datastore] = vsanDatastore
|
|
||||||
framework.Logf("Invoking test for SPBM storage policy: %+v", scParameters)
|
|
||||||
kubernetesClusterName := GetAndExpectStringEnvVar(KubernetesClusterName)
|
|
||||||
controlPlaneNode, err := getControlPlaneNode(ctx, client)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
invokeStaleDummyVMTestWithStoragePolicy(ctx, client, controlPlaneNode, namespace, kubernetesClusterName, scParameters)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify if a SPBM policy is not honored on a non-compatible datastore for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for SPBM policy: %s and datastore: %s", tagPolicy, vsanDatastore))
|
|
||||||
scParameters[SpbmStoragePolicy] = tagPolicy
|
|
||||||
scParameters[Datastore] = vsanDatastore
|
|
||||||
scParameters[DiskFormat] = ThinDisk
|
|
||||||
framework.Logf("Invoking test for SPBM storage policy on a non-compatible datastore: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "User specified datastore is not compatible with the storagePolicy: \\\"" + tagPolicy + "\\\""
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify if a non-existing SPBM policy is not honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for SPBM policy: %s", BronzeStoragePolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = BronzeStoragePolicy
|
|
||||||
scParameters[DiskFormat] = ThinDisk
|
|
||||||
framework.Logf("Invoking test for non-existing SPBM storage policy: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "no pbm profile found with name: \\\"" + BronzeStoragePolicy + "\\"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("verify an if a SPBM policy and VSAN capabilities cannot be honored for dynamically provisioned pvc using storageclass", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Invoking test for SPBM policy: %s with VSAN storage capabilities", policyName))
|
|
||||||
scParameters[SpbmStoragePolicy] = policyName
|
|
||||||
gomega.Expect(scParameters[SpbmStoragePolicy]).NotTo(gomega.BeEmpty())
|
|
||||||
scParameters[PolicyDiskStripes] = DiskStripesCapabilityVal
|
|
||||||
scParameters[DiskFormat] = ThinDisk
|
|
||||||
framework.Logf("Invoking test for SPBM storage policy and VSAN capabilities together: %+v", scParameters)
|
|
||||||
err := invokeInvalidPolicyTestNeg(ctx, client, namespace, scParameters)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "Cannot specify storage policy capabilities along with storage policy name. Please specify only one"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func invokeValidPolicyTest(ctx context.Context, f *framework.Framework, client clientset.Interface, namespace string, scParameters map[string]string) {
|
|
||||||
ginkgo.By("Creating Storage Class With storage policy params")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("storagepolicysc", scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod to attach PV to the node")
|
|
||||||
// Create pod to attach Volume to Node
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, nil, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verify the volume is accessible and available in the pod")
|
|
||||||
verifyVSphereVolumesAccessible(ctx, client, pod, persistentvolumes)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, client, pod))
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for volumes to be detached from the node")
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName))
|
|
||||||
}
|
|
||||||
|
|
||||||
func invokeInvalidPolicyTestNeg(ctx context.Context, client clientset.Interface, namespace string, scParameters map[string]string) error {
|
|
||||||
ginkgo.By("Creating Storage Class With storage policy params")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("storagepolicysc", scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, pvclaim.Namespace, pvclaim.Name, framework.Poll, 2*time.Minute)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
|
|
||||||
eventList, err := client.CoreV1().Events(pvclaim.Namespace).List(ctx, metav1.ListOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
return fmt.Errorf("Failure message: %+q", eventList.Items[0].Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// invokeStaleDummyVMTestWithStoragePolicy assumes control plane node is present on the datacenter specified in the workspace section of vsphere.conf file.
|
|
||||||
// With in-tree VCP, when the volume is created using storage policy, shadow (dummy) VM is getting created and deleted to apply SPBM policy on the volume.
|
|
||||||
func invokeStaleDummyVMTestWithStoragePolicy(ctx context.Context, client clientset.Interface, controlPlaneNode string, namespace string, clusterName string, scParameters map[string]string) {
|
|
||||||
ginkgo.By("Creating Storage Class With storage policy params")
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("storagepolicysc", scParameters, nil, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(framework.IgnoreNotFound(client.StorageV1().StorageClasses().Delete), storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
ginkgo.By("Expect claim to fail provisioning volume")
|
|
||||||
_, err = e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, 2*time.Minute)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
|
|
||||||
updatedClaim, err := client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvclaim.Name, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
vmName := clusterName + "-dynamic-pvc-" + string(updatedClaim.UID)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
// Wait for 6 minutes to let the vSphere Cloud Provider clean up routine delete the dummy VM
|
|
||||||
time.Sleep(6 * time.Minute)
|
|
||||||
|
|
||||||
fnvHash := fnv.New32a()
|
|
||||||
fnvHash.Write([]byte(vmName))
|
|
||||||
dummyVMFullName := dummyVMPrefixName + "-" + fmt.Sprint(fnvHash.Sum32())
|
|
||||||
errorMsg := "Dummy VM - " + vmName + " is still present. Failing the test.."
|
|
||||||
nodeInfo := TestContext.NodeMapper.GetNodeInfo(controlPlaneNode)
|
|
||||||
isVMPresentFlag, err := nodeInfo.VSphere.IsVMPresent(dummyVMFullName, nodeInfo.DataCenterRef)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
if isVMPresentFlag {
|
|
||||||
framework.Failf("VM with name %s is present, %s", dummyVMFullName, errorMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getControlPlaneNode(ctx context.Context, client clientset.Interface) (string, error) {
|
|
||||||
regKubeScheduler := regexp.MustCompile("kube-scheduler-.*")
|
|
||||||
regKubeControllerManager := regexp.MustCompile("kube-controller-manager-.*")
|
|
||||||
|
|
||||||
podList, err := client.CoreV1().Pods(metav1.NamespaceSystem).List(ctx, metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if len(podList.Items) < 1 {
|
|
||||||
return "", fmt.Errorf("could not find any pods in namespace %s to grab metrics from", metav1.NamespaceSystem)
|
|
||||||
}
|
|
||||||
for _, pod := range podList.Items {
|
|
||||||
if regKubeScheduler.MatchString(pod.Name) || regKubeControllerManager.MatchString(pod.Name) {
|
|
||||||
return pod.Spec.NodeName, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("could not find any nodes where control plane pods are running")
|
|
||||||
}
|
|
||||||
@@ -1,537 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2019 The Kubernetes 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 vsphere
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
|
||||||
"github.com/onsi/gomega"
|
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
storagev1 "k8s.io/api/storage/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
volumeevents "k8s.io/kubernetes/pkg/controller/volume/events"
|
|
||||||
"k8s.io/kubernetes/test/e2e/feature"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
|
||||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
|
||||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
|
||||||
"k8s.io/kubernetes/test/e2e/storage/utils"
|
|
||||||
admissionapi "k8s.io/pod-security-admission/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test to verify multi-zone support for dynamic volume provisioning in kubernetes.
|
|
||||||
The test environment is illustrated below:
|
|
||||||
|
|
||||||
datacenter-1
|
|
||||||
--->cluster-vsan-1 (zone-a) ____________________ _________________
|
|
||||||
--->host-1 : master | | | |
|
|
||||||
--->host-2 : node1 ___________________ | | | |
|
|
||||||
--->host-3 (zone-c): node2 | || vsanDatastore | | |
|
|
||||||
| localDatastore || | | |
|
|
||||||
|___________________||____________________| | sharedVmfs-0 |
|
|
||||||
--->cluster-vsan-2 (zone-b) ____________________ | |
|
|
||||||
--->host-4 : node3 | | | |
|
|
||||||
--->host-5 : node4 | vsanDatastore (1) | | |
|
|
||||||
--->host-6 | | | |
|
|
||||||
|____________________| |_________________|
|
|
||||||
--->cluster-3 (zone-c) ___________________
|
|
||||||
--->host-7 : node5 | |
|
|
||||||
| localDatastore (1)|
|
|
||||||
|___________________|
|
|
||||||
datacenter-2
|
|
||||||
--->cluster-1 (zone-d) ___________________
|
|
||||||
--->host-8 : node6 | |
|
|
||||||
| localDatastore |
|
|
||||||
|___________________|
|
|
||||||
|
|
||||||
Testbed description :
|
|
||||||
1. cluster-vsan-1 is tagged with zone-a. So, vsanDatastore inherits zone-a since all the hosts under zone-a have vsanDatastore mounted on them.
|
|
||||||
2. cluster-vsan-2 is tagged with zone-b. So, vsanDatastore (1) inherits zone-b since all the hosts under zone-b have vsanDatastore (1) mounted on them.
|
|
||||||
3. sharedVmfs-0 inherits both zone-a and zone-b since all the hosts in both zone-a and zone-b have this datastore mounted on them.
|
|
||||||
4. cluster-3 is tagged with zone-c. cluster-3 only contains host-7.
|
|
||||||
5. host-3 under cluster-vsan-1 is tagged with zone-c.
|
|
||||||
6. Since there are no shared datastores between host-7 under cluster-3 and host-3 under cluster-vsan-1, no datastores in the environment inherit zone-c.
|
|
||||||
7. host-8 under datacenter-2 and cluster-1 is tagged with zone-d. So, localDatastore attached to host-8 inherits zone-d.
|
|
||||||
8. The six worker nodes are distributed among the hosts as shown in the above illustration.
|
|
||||||
9. Two storage policies are created on VC. One is a VSAN storage policy named as compatpolicy with hostFailuresToTolerate capability set to 1.
|
|
||||||
|
|
||||||
Testsuite description :
|
|
||||||
1. Tests to verify that zone labels are set correctly on a dynamically created PV.
|
|
||||||
2. Tests to verify dynamic pv creation fails if availability zones are not specified or if there are no shared datastores under the specified zones.
|
|
||||||
3. Tests to verify dynamic pv creation using availability zones works in combination with other storage class parameters such as storage policy,
|
|
||||||
datastore and VSAN capabilities.
|
|
||||||
4. Tests to verify dynamic pv creation using availability zones fails in combination with other storage class parameters such as storage policy,
|
|
||||||
datastore and VSAN capabilities specifications when any of the former mentioned parameters are incompatible with the rest.
|
|
||||||
5. Tests to verify dynamic pv creation using availability zones work across different datacenters in the same VC.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _ = utils.SIGDescribe("Zone Support", feature.Vsphere, func() {
|
|
||||||
f := framework.NewDefaultFramework("zone-support")
|
|
||||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
|
||||||
var (
|
|
||||||
scParameters map[string]string
|
|
||||||
zones []string
|
|
||||||
vsanDatastore1 string
|
|
||||||
vsanDatastore2 string
|
|
||||||
localDatastore string
|
|
||||||
compatPolicy string
|
|
||||||
nonCompatPolicy string
|
|
||||||
zoneA string
|
|
||||||
zoneB string
|
|
||||||
zoneC string
|
|
||||||
zoneD string
|
|
||||||
invalidZone string
|
|
||||||
)
|
|
||||||
ginkgo.BeforeEach(func(ctx context.Context) {
|
|
||||||
e2eskipper.SkipUnlessProviderIs("vsphere")
|
|
||||||
Bootstrap(f)
|
|
||||||
e2eskipper.SkipUnlessMultizone(ctx, f.ClientSet)
|
|
||||||
vsanDatastore1 = GetAndExpectStringEnvVar(VCPZoneVsanDatastore1)
|
|
||||||
vsanDatastore2 = GetAndExpectStringEnvVar(VCPZoneVsanDatastore2)
|
|
||||||
localDatastore = GetAndExpectStringEnvVar(VCPZoneLocalDatastore)
|
|
||||||
compatPolicy = GetAndExpectStringEnvVar(VCPZoneCompatPolicyName)
|
|
||||||
nonCompatPolicy = GetAndExpectStringEnvVar(VCPZoneNonCompatPolicyName)
|
|
||||||
zoneA = GetAndExpectStringEnvVar(VCPZoneA)
|
|
||||||
zoneB = GetAndExpectStringEnvVar(VCPZoneB)
|
|
||||||
zoneC = GetAndExpectStringEnvVar(VCPZoneC)
|
|
||||||
zoneD = GetAndExpectStringEnvVar(VCPZoneD)
|
|
||||||
invalidZone = GetAndExpectStringEnvVar(VCPInvalidZone)
|
|
||||||
scParameters = make(map[string]string)
|
|
||||||
zones = make([]string, 0)
|
|
||||||
_, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify dynamically created pv with allowed zones specified in storage class, shows the right zone information on its labels", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with the following zones : %s", zoneA))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVZoneLabels(ctx, f, nil, zones)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify dynamically created pv with multiple zones specified in the storage class, shows both the zones on its labels", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with the following zones : %s, %s", zoneA, zoneB))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
zones = append(zones, zoneB)
|
|
||||||
verifyPVZoneLabels(ctx, f, nil, zones)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with invalid zone specified in storage class fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with unknown zone : %s", invalidZone))
|
|
||||||
zones = append(zones, invalidZone)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, nil, zones, "")
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "Failed to find a shared datastore matching zone [" + invalidZone + "]"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on allowed zones specified in storage class ", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zones :%s", zoneA))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, nil, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on multiple zones specified in storage class ", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zones :%s, %s", zoneA, zoneB))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
zones = append(zones, zoneB)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, nil, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on the allowed zones and datastore specified in storage class", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s and datastore :%s", zoneA, vsanDatastore1))
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with incompatible datastore and zone combination specified in storage class fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s and datastore :%s", zoneC, vsanDatastore1))
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
zones = append(zones, zoneC)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, zones, "")
|
|
||||||
errorMsg := "No matching datastores found in the kubernetes cluster for zone " + zoneC
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on the allowed zones and storage policy specified in storage class", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s and storage policy :%s", zoneA, compatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created on a non-Workspace zone and attached to a dynamically created PV, based on the allowed zones and storage policy specified in storage class", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s and storage policy :%s", zoneB, compatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
zones = append(zones, zoneB)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with incompatible storagePolicy and zone combination specified in storage class fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s and storage policy :%s", zoneA, nonCompatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = nonCompatPolicy
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, zones, "")
|
|
||||||
errorMsg := "No compatible datastores found that satisfy the storage policy requirements"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on the allowed zones, datastore and storage policy specified in storage class", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s datastore :%s and storagePolicy :%s", zoneA, vsanDatastore1, compatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with incompatible storage policy along with compatible zone and datastore combination specified in storage class fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s datastore :%s and storagePolicy :%s", zoneA, vsanDatastore1, nonCompatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = nonCompatPolicy
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, zones, "")
|
|
||||||
errorMsg := "User specified datastore is not compatible with the storagePolicy: \\\"" + nonCompatPolicy + "\\\"."
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with incompatible zone along with compatible storagePolicy and datastore combination specified in storage class fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s datastore :%s and storagePolicy :%s", zoneC, vsanDatastore2, compatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
scParameters[Datastore] = vsanDatastore2
|
|
||||||
zones = append(zones, zoneC)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, zones, "")
|
|
||||||
errorMsg := "No matching datastores found in the kubernetes cluster for zone " + zoneC
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation fails if no zones are specified in the storage class (No shared datastores exist among all the nodes)", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with no zones"))
|
|
||||||
err := verifyPVCCreationFails(ctx, f, nil, nil, "")
|
|
||||||
errorMsg := "No shared datastores found in the Kubernetes cluster"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation fails if only datastore is specified in the storage class (No shared datastores exist among all the nodes)", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with datastore :%s", vsanDatastore1))
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, nil, "")
|
|
||||||
errorMsg := "No shared datastores found in the Kubernetes cluster"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation fails if only storage policy is specified in the storage class (No shared datastores exist among all the nodes)", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with storage policy :%s", compatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, nil, "")
|
|
||||||
errorMsg := "No shared datastores found in the Kubernetes cluster"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with compatible policy and datastore without any zones specified in the storage class fails (No shared datastores exist among all the nodes)", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with storage policy :%s and datastore :%s", compatPolicy, vsanDatastore1))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, nil, "")
|
|
||||||
errorMsg := "No shared datastores found in the Kubernetes cluster"
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation fails if the availability zone specified in the storage class have no shared datastores under it.", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s", zoneC))
|
|
||||||
zones = append(zones, zoneC)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, nil, zones, "")
|
|
||||||
errorMsg := "No matching datastores found in the kubernetes cluster for zone " + zoneC
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on multiple zones specified in the storage class. (No shared datastores exist among both zones)", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with the following zones :%s and %s", zoneA, zoneC))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
zones = append(zones, zoneC)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, nil, zones, "")
|
|
||||||
errorMsg := "No matching datastores found in the kubernetes cluster for zone " + zoneC
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify PVC creation with an invalid VSAN capability along with a compatible zone combination specified in storage class fails", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with %s :%s and zone :%s", PolicyHostFailuresToTolerate, HostFailuresToTolerateCapabilityInvalidVal, zoneA))
|
|
||||||
scParameters[PolicyHostFailuresToTolerate] = HostFailuresToTolerateCapabilityInvalidVal
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
err := verifyPVCCreationFails(ctx, f, scParameters, zones, "")
|
|
||||||
errorMsg := "Invalid value for " + PolicyHostFailuresToTolerate + "."
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on a VSAN capability, datastore and compatible zone specified in storage class", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with %s :%s, %s :%s, datastore :%s and zone :%s", PolicyObjectSpaceReservation, ObjectSpaceReservationCapabilityVal, PolicyIopsLimit, IopsLimitCapabilityVal, vsanDatastore1, zoneA))
|
|
||||||
scParameters[PolicyObjectSpaceReservation] = ObjectSpaceReservationCapabilityVal
|
|
||||||
scParameters[PolicyIopsLimit] = IopsLimitCapabilityVal
|
|
||||||
scParameters[Datastore] = vsanDatastore1
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on the allowed zones specified in storage class when the datastore under the zone is present in another datacenter", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s", zoneD))
|
|
||||||
zones = append(zones, zoneD)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV, based on the allowed zones and datastore specified in storage class when there are multiple datastores with the same name under different zones across datacenters", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with zone :%s and datastore name :%s", zoneD, localDatastore))
|
|
||||||
scParameters[Datastore] = localDatastore
|
|
||||||
zones = append(zones, zoneD)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, "")
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV with storage policy specified in storage class in waitForFirstConsumer binding mode", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with waitForFirstConsumer mode and storage policy :%s", compatPolicy))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, nil, storagev1.VolumeBindingWaitForFirstConsumer)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV with storage policy specified in storage class in waitForFirstConsumer binding mode with allowedTopologies", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with waitForFirstConsumer mode, storage policy :%s and zone :%s", compatPolicy, zoneA))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, scParameters, zones, storagev1.VolumeBindingWaitForFirstConsumer)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod is created and attached to a dynamically created PV with storage policy specified in storage class in waitForFirstConsumer binding mode with multiple allowedTopologies", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with waitForFirstConsumer mode and zones : %s, %s", zoneA, zoneB))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
zones = append(zones, zoneB)
|
|
||||||
verifyPVCAndPodCreationSucceeds(ctx, f, nil, zones, storagev1.VolumeBindingWaitForFirstConsumer)
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a PVC creation fails when multiple zones are specified in the storage class without shared datastores among the zones in waitForFirstConsumer binding mode", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with waitForFirstConsumer mode and following zones :%s and %s", zoneA, zoneC))
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
zones = append(zones, zoneC)
|
|
||||||
err := verifyPodAndPvcCreationFailureOnWaitForFirstConsumerMode(ctx, f, nil, zones)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
errorMsg := "No matching datastores found in the kubernetes cluster for zone " + zoneC
|
|
||||||
if !strings.Contains(err.Error(), errorMsg) {
|
|
||||||
framework.ExpectNoError(err, errorMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ginkgo.It("Verify a pod fails to get scheduled when conflicting volume topology (allowedTopologies) and pod scheduling constraints(nodeSelector) are specified", func(ctx context.Context) {
|
|
||||||
ginkgo.By(fmt.Sprintf("Creating storage class with waitForFirstConsumerMode, storage policy :%s and zone :%s", compatPolicy, zoneA))
|
|
||||||
scParameters[SpbmStoragePolicy] = compatPolicy
|
|
||||||
// allowedTopologies set as zoneA
|
|
||||||
zones = append(zones, zoneA)
|
|
||||||
nodeSelectorMap := map[string]string{
|
|
||||||
// nodeSelector set as zoneB
|
|
||||||
v1.LabelTopologyZone: zoneB,
|
|
||||||
}
|
|
||||||
verifyPodSchedulingFails(ctx, f, nodeSelectorMap, scParameters, zones, storagev1.VolumeBindingWaitForFirstConsumer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func verifyPVCAndPodCreationSucceeds(ctx context.Context, f *framework.Framework, scParameters map[string]string, zones []string, volumeBindingMode storagev1.VolumeBindingMode) {
|
|
||||||
client := f.ClientSet
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
timeouts := f.Timeouts
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("zone-sc", scParameters, zones, volumeBindingMode), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(client.StorageV1().StorageClasses().Delete, storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
|
|
||||||
var persistentvolumes []*v1.PersistentVolume
|
|
||||||
// If WaitForFirstConsumer mode, verify pvc binding status after pod creation. For immediate mode, do now.
|
|
||||||
if volumeBindingMode != storagev1.VolumeBindingWaitForFirstConsumer {
|
|
||||||
persistentvolumes = waitForPVClaimBoundPhase(ctx, client, pvclaims, timeouts.ClaimProvision)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Creating pod to attach PV to the node")
|
|
||||||
pod, err := e2epod.CreatePod(ctx, client, namespace, nil, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
if volumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer {
|
|
||||||
persistentvolumes = waitForPVClaimBoundPhase(ctx, client, pvclaims, timeouts.ClaimProvision)
|
|
||||||
}
|
|
||||||
|
|
||||||
if zones != nil {
|
|
||||||
ginkgo.By("Verify persistent volume was created on the right zone")
|
|
||||||
verifyVolumeCreationOnRightZone(ctx, persistentvolumes, pod.Spec.NodeName, zones)
|
|
||||||
}
|
|
||||||
|
|
||||||
ginkgo.By("Verify the volume is accessible and available in the pod")
|
|
||||||
verifyVSphereVolumesAccessible(ctx, client, pod, persistentvolumes)
|
|
||||||
|
|
||||||
ginkgo.By("Deleting pod")
|
|
||||||
framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, client, pod))
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for volumes to be detached from the node")
|
|
||||||
framework.ExpectNoError(waitForVSphereDiskToDetach(ctx, persistentvolumes[0].Spec.VsphereVolume.VolumePath, pod.Spec.NodeName))
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPodAndPvcCreationFailureOnWaitForFirstConsumerMode(ctx context.Context, f *framework.Framework, scParameters map[string]string, zones []string) error {
|
|
||||||
client := f.ClientSet
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("zone-sc", scParameters, zones, storagev1.VolumeBindingWaitForFirstConsumer), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(client.StorageV1().StorageClasses().Delete, storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
|
|
||||||
ginkgo.By("Creating a pod")
|
|
||||||
pod := e2epod.MakePod(namespace, nil, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
pod, err = client.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epod.DeletePodWithWait, client, pod)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, pvclaim.Namespace, pvclaim.Name, framework.Poll, 2*time.Minute)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
|
|
||||||
eventList, err := client.CoreV1().Events(pvclaim.Namespace).List(ctx, metav1.ListOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
// Look for PVC ProvisioningFailed event and return the message.
|
|
||||||
for _, event := range eventList.Items {
|
|
||||||
if event.Source.Component == "persistentvolume-controller" && event.Reason == volumeevents.ProvisioningFailed {
|
|
||||||
return fmt.Errorf("Failure message: %s", event.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForPVClaimBoundPhase(ctx context.Context, client clientset.Interface, pvclaims []*v1.PersistentVolumeClaim, timeout time.Duration) []*v1.PersistentVolume {
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, timeout)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
return persistentvolumes
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPodSchedulingFails(ctx context.Context, f *framework.Framework, nodeSelector map[string]string, scParameters map[string]string, zones []string, volumeBindingMode storagev1.VolumeBindingMode) {
|
|
||||||
client := f.ClientSet
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("zone-sc", scParameters, zones, volumeBindingMode), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(client.StorageV1().StorageClasses().Delete, storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
|
|
||||||
ginkgo.By("Creating a pod")
|
|
||||||
pod, err := e2epod.CreateUnschedulablePod(ctx, client, namespace, nodeSelector, pvclaims, f.NamespacePodSecurityLevel, "")
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epod.DeletePodWithWait, client, pod)
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPVCCreationFails(ctx context.Context, f *framework.Framework, scParameters map[string]string, zones []string, volumeBindingMode storagev1.VolumeBindingMode) error {
|
|
||||||
client := f.ClientSet
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("zone-sc", scParameters, zones, volumeBindingMode), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(client.StorageV1().StorageClasses().Delete, storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the Storage Class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, pvclaim.Namespace, pvclaim.Name, framework.Poll, 2*time.Minute)
|
|
||||||
framework.ExpectError(err)
|
|
||||||
|
|
||||||
eventList, err := client.CoreV1().Events(pvclaim.Namespace).List(ctx, metav1.ListOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
framework.Logf("Failure message : %+q", eventList.Items[0].Message)
|
|
||||||
return fmt.Errorf("Failure message: %+q", eventList.Items[0].Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPVZoneLabels(ctx context.Context, f *framework.Framework, scParameters map[string]string, zones []string) {
|
|
||||||
client := f.ClientSet
|
|
||||||
namespace := f.Namespace.Name
|
|
||||||
storageclass, err := client.StorageV1().StorageClasses().Create(ctx, getVSphereStorageClassSpec("zone-sc", nil, zones, ""), metav1.CreateOptions{})
|
|
||||||
framework.ExpectNoError(err, fmt.Sprintf("Failed to create storage class with err: %v", err))
|
|
||||||
ginkgo.DeferCleanup(client.StorageV1().StorageClasses().Delete, storageclass.Name, metav1.DeleteOptions{})
|
|
||||||
|
|
||||||
ginkgo.By("Creating PVC using the storage class")
|
|
||||||
pvclaim, err := e2epv.CreatePVC(ctx, client, namespace, getVSphereClaimSpecWithStorageClass(namespace, "2Gi", storageclass))
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, client, pvclaim.Name, namespace)
|
|
||||||
|
|
||||||
var pvclaims []*v1.PersistentVolumeClaim
|
|
||||||
pvclaims = append(pvclaims, pvclaim)
|
|
||||||
ginkgo.By("Waiting for claim to be in bound phase")
|
|
||||||
persistentvolumes, err := e2epv.WaitForPVClaimBoundPhase(ctx, client, pvclaims, f.Timeouts.ClaimProvision)
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
|
|
||||||
ginkgo.By("Verify zone information is present in the volume labels")
|
|
||||||
for _, pv := range persistentvolumes {
|
|
||||||
// Multiple zones are separated with "__"
|
|
||||||
pvZoneLabels := strings.Split(pv.ObjectMeta.Labels[v1.LabelTopologyZone], "__")
|
|
||||||
for _, zone := range zones {
|
|
||||||
gomega.Expect(pvZoneLabels).Should(gomega.ContainElement(zone), "Incorrect or missing zone labels in pv.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
vendor/github.com/vmware/govmomi/.dockerignore
generated
vendored
2
vendor/github.com/vmware/govmomi/.dockerignore
generated
vendored
@@ -1,2 +0,0 @@
|
|||||||
Dockerfile*
|
|
||||||
.*ignore
|
|
||||||
13
vendor/github.com/vmware/govmomi/.gitignore
generated
vendored
13
vendor/github.com/vmware/govmomi/.gitignore
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
secrets.yml
|
|
||||||
dist/
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# ignore tools binaries
|
|
||||||
/git-chglog
|
|
||||||
|
|
||||||
# ignore RELEASE-specific CHANGELOG
|
|
||||||
/RELEASE_CHANGELOG.md
|
|
||||||
|
|
||||||
# Ignore editor temp files
|
|
||||||
*~
|
|
||||||
.vscode/
|
|
||||||
18
vendor/github.com/vmware/govmomi/.golangci.yml
generated
vendored
18
vendor/github.com/vmware/govmomi/.golangci.yml
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
linters:
|
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- goimports
|
|
||||||
- govet
|
|
||||||
# Run with --fast=false for more extensive checks
|
|
||||||
fast: true
|
|
||||||
# override defaults
|
|
||||||
linters-settings:
|
|
||||||
goimports:
|
|
||||||
# put imports beginning with prefix after 3rd-party packages;
|
|
||||||
# it's a comma-separated list of prefixes
|
|
||||||
local-prefixes: github.com/vmware/govmomi
|
|
||||||
run:
|
|
||||||
timeout: 6m
|
|
||||||
skip-dirs:
|
|
||||||
- vim25/xml
|
|
||||||
- cns/types
|
|
||||||
151
vendor/github.com/vmware/govmomi/.goreleaser.yml
generated
vendored
151
vendor/github.com/vmware/govmomi/.goreleaser.yml
generated
vendored
@@ -1,151 +0,0 @@
|
|||||||
---
|
|
||||||
project_name: govmomi
|
|
||||||
|
|
||||||
builds:
|
|
||||||
- id: govc
|
|
||||||
goos: &goos-defs
|
|
||||||
- linux
|
|
||||||
- darwin
|
|
||||||
- windows
|
|
||||||
- freebsd
|
|
||||||
goarch: &goarch-defs
|
|
||||||
- amd64
|
|
||||||
- arm
|
|
||||||
- arm64
|
|
||||||
- mips64le
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
- PKGPATH=github.com/vmware/govmomi/govc/flags
|
|
||||||
main: ./govc/main.go
|
|
||||||
binary: govc
|
|
||||||
ldflags:
|
|
||||||
- "-X {{.Env.PKGPATH}}.BuildVersion={{.Version}} -X {{.Env.PKGPATH}}.BuildCommit={{.ShortCommit}} -X {{.Env.PKGPATH}}.BuildDate={{.Date}}"
|
|
||||||
- id: vcsim
|
|
||||||
goos: *goos-defs
|
|
||||||
goarch: *goarch-defs
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
main: ./vcsim/main.go
|
|
||||||
binary: vcsim
|
|
||||||
ldflags:
|
|
||||||
- "-X main.buildVersion={{.Version}} -X main.buildCommit={{.ShortCommit}} -X main.buildDate={{.Date}}"
|
|
||||||
|
|
||||||
archives:
|
|
||||||
- id: govcbuild
|
|
||||||
builds:
|
|
||||||
- govc
|
|
||||||
name_template: >-
|
|
||||||
govc_
|
|
||||||
{{- title .Os }}_
|
|
||||||
{{- if eq .Arch "amd64" }}x86_64
|
|
||||||
{{- else if eq .Arch "386" }}i386
|
|
||||||
{{- else }}{{ .Arch }}{{ end }}
|
|
||||||
format_overrides: &overrides
|
|
||||||
- goos: windows
|
|
||||||
format: zip
|
|
||||||
files: &extrafiles
|
|
||||||
- CHANGELOG.md
|
|
||||||
- LICENSE.txt
|
|
||||||
- README.md
|
|
||||||
|
|
||||||
- id: vcsimbuild
|
|
||||||
builds:
|
|
||||||
- vcsim
|
|
||||||
name_template: >-
|
|
||||||
vcsim_
|
|
||||||
{{- title .Os }}_
|
|
||||||
{{- if eq .Arch "amd64" }}x86_64
|
|
||||||
{{- else if eq .Arch "386" }}i386
|
|
||||||
{{- else }}{{ .Arch }}{{ end }}
|
|
||||||
format_overrides: *overrides
|
|
||||||
files: *extrafiles
|
|
||||||
|
|
||||||
snapshot:
|
|
||||||
name_template: "{{ .Tag }}-next"
|
|
||||||
|
|
||||||
checksum:
|
|
||||||
name_template: "checksums.txt"
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- "^docs:"
|
|
||||||
- "^test:"
|
|
||||||
- Merge pull request
|
|
||||||
- Merge branch
|
|
||||||
|
|
||||||
# upload disabled since it is maintained in homebrew-core
|
|
||||||
brews:
|
|
||||||
- name: govc
|
|
||||||
ids:
|
|
||||||
- govcbuild
|
|
||||||
tap:
|
|
||||||
owner: govmomi
|
|
||||||
name: homebrew-tap
|
|
||||||
# TODO: create token in specified tap repo, add as secret to govmomi repo and reference in release workflow
|
|
||||||
# token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
|
|
||||||
# enable once we do fully automated releases
|
|
||||||
skip_upload: true
|
|
||||||
commit_author:
|
|
||||||
name: Alfred the Narwhal
|
|
||||||
email: cna-alfred@vmware.com
|
|
||||||
folder: Formula
|
|
||||||
homepage: "https://github.com/vmware/govmomi/blob/master/govc/README.md"
|
|
||||||
description: "govc is a vSphere CLI built on top of govmomi."
|
|
||||||
test: |
|
|
||||||
system "#{bin}/govc version"
|
|
||||||
install: |
|
|
||||||
bin.install "govc"
|
|
||||||
- name: vcsim
|
|
||||||
ids:
|
|
||||||
- vcsimbuild
|
|
||||||
tap:
|
|
||||||
owner: govmomi
|
|
||||||
name: homebrew-tap
|
|
||||||
# TODO: create token in specified tap repo, add as secret to govmomi repo and reference in release workflow
|
|
||||||
# token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
|
|
||||||
# enable once we do fully automated releases
|
|
||||||
skip_upload: true
|
|
||||||
commit_author:
|
|
||||||
name: Alfred the Narwhal
|
|
||||||
email: cna-alfred@vmware.com
|
|
||||||
folder: Formula
|
|
||||||
homepage: "https://github.com/vmware/govmomi/blob/master/vcsim/README.md"
|
|
||||||
description: "vcsim is a vSphere API simulator built on top of govmomi."
|
|
||||||
test: |
|
|
||||||
system "#{bin}/vcsim -h"
|
|
||||||
install: |
|
|
||||||
bin.install "vcsim"
|
|
||||||
|
|
||||||
dockers:
|
|
||||||
- image_templates:
|
|
||||||
- "vmware/govc:{{ .Tag }}"
|
|
||||||
- "vmware/govc:{{ .ShortCommit }}"
|
|
||||||
- "vmware/govc:latest"
|
|
||||||
dockerfile: Dockerfile.govc
|
|
||||||
ids:
|
|
||||||
- govc
|
|
||||||
build_flag_templates:
|
|
||||||
- "--pull"
|
|
||||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
|
||||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
|
||||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
|
||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
|
||||||
- "--label=org.opencontainers.image.url=https://github.com/vmware/govmomi"
|
|
||||||
- "--platform=linux/amd64"
|
|
||||||
- image_templates:
|
|
||||||
- "vmware/vcsim:{{ .Tag }}"
|
|
||||||
- "vmware/vcsim:{{ .ShortCommit }}"
|
|
||||||
- "vmware/vcsim:latest"
|
|
||||||
dockerfile: Dockerfile.vcsim
|
|
||||||
ids:
|
|
||||||
- vcsim
|
|
||||||
build_flag_templates:
|
|
||||||
- "--pull"
|
|
||||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
|
||||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
|
||||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
|
||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
|
||||||
- "--label=org.opencontainers.image.url=https://github.com/vmware/govmomi"
|
|
||||||
- "--platform=linux/amd64"
|
|
||||||
45
vendor/github.com/vmware/govmomi/.mailmap
generated
vendored
45
vendor/github.com/vmware/govmomi/.mailmap
generated
vendored
@@ -1,45 +0,0 @@
|
|||||||
amanpaha <amanpahariya@microsoft.com> amanpaha <84718160+amanpaha@users.noreply.github.com>
|
|
||||||
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br> Amanda Hager Lopes de Andrade Katz <amanda.katz@serpro.gov.br>
|
|
||||||
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br> amandahla <amanda.andrade@serpro.gov.br>
|
|
||||||
Amit Bathla <abathla@.vmware.com> <abathla@promb-1s-dhcp216.eng.vmware.com>
|
|
||||||
Andrew Kutz <akutz@vmware.com> <sakutz@gmail.com>
|
|
||||||
Andrew Kutz <akutz@vmware.com> akutz <akutz@vmware.com>
|
|
||||||
Andrew Kutz <akutz@vmware.com> Andrew Kutz <101085+akutz@users.noreply.github.com>
|
|
||||||
Anfernee Yongkun Gui <agui@vmware.com> <anfernee.gui@gmail.com>
|
|
||||||
Anfernee Yongkun Gui <agui@vmware.com> Yongkun Anfernee Gui <agui@vmware.com>
|
|
||||||
Anna Carrigan <anna.carrigan@hpe.com> Anna <anna.carrigan@outlook.com>
|
|
||||||
Balu Dontu <bdontu@vmware.com> BaluDontu <bdontu@vmware.com>
|
|
||||||
Bruce Downs <bruceadowns@gmail.com> <bdowns@vmware.com>
|
|
||||||
Bruce Downs <bruceadowns@gmail.com> <bruce.downs@autodesk.com>
|
|
||||||
Bruce Downs <bruceadowns@gmail.com> <bruce.downs@jivesoftware.com>
|
|
||||||
Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com>
|
|
||||||
Cédric Blomart <cblomart@gmail.com> <cedric.blomart@minfin.fed.be>
|
|
||||||
Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com>
|
|
||||||
David Stark <dave@davidstark.name> <david.stark@bskyb.com>
|
|
||||||
Doug MacEachern <dougm@vmware.com> dougm <dougm@users.noreply.github.com>
|
|
||||||
Eric Gray <egray@vmware.com> <ericgray@users.noreply.github.com>
|
|
||||||
Eric Yutao <eric.yutao@gmail.com> eric <eric.yutao@gmail.com>
|
|
||||||
Fabio Rapposelli <fabio@vmware.com> <fabio@rapposelli.org>
|
|
||||||
Faiyaz Ahmed <faiyaza@vmware.com> Faiyaz Ahmed <ahmedf@vmware.com>
|
|
||||||
Faiyaz Ahmed <faiyaza@vmware.com> Faiyaz Ahmed <faiyaza@gmail.com>
|
|
||||||
Faiyaz Ahmed <faiyaza@vmware.com> Faiyaz Ahmed <fdawg4l@users.noreply.github.com>
|
|
||||||
Henrik Hodne <henrik@travis-ci.com> <henrik@hodne.io>
|
|
||||||
Ian Eyberg <ian@deferpanic.com> <ian@opuler.com>
|
|
||||||
Jeremy Canady <jcanady@jackhenry.com> <jcanady@gmail.com>
|
|
||||||
Jiatong Wang <wjiatong@vmware.com> jiatongw <wjiatong@vmware.com>
|
|
||||||
Lintong Jiang <lintongj@vmware.com> lintongj <55512168+lintongj@users.noreply.github.com>
|
|
||||||
Michael Gasch <mgasch@vmware.com> Michael Gasch <embano1@live.com>
|
|
||||||
Mincho Tonev <mtonev@vmware.com> matonev <31008054+matonev@users.noreply.github.com>
|
|
||||||
Parveen Chahal <parkuma@microsoft.com> <mail.chahal@gmail.com>
|
|
||||||
Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com>
|
|
||||||
Saad Malik <saad@spectrocloud.com> <simfox3@gmail.com>
|
|
||||||
Takaaki Furukawa <takaaki.frkw@gmail.com> takaaki.furukawa <takaaki.furukawa@mail.rakuten.com>
|
|
||||||
Takaaki Furukawa <takaaki.frkw@gmail.com> tkak <takaaki.frkw@gmail.com>
|
|
||||||
Uwe Bessle <Uwe.Bessle@iteratec.de> Uwe Bessle <u.bessle.extern@eos-ts.com>
|
|
||||||
Uwe Bessle <Uwe.Bessle@iteratec.de> Uwe Bessle <uwe.bessle@web.de>
|
|
||||||
Vadim Egorov <vegorov@vmware.com> <egorovv@gmail.com>
|
|
||||||
William Lam <wlam@vmware.com> <info.virtuallyghetto@gmail.com>
|
|
||||||
Yun Zhou <yunz@vmware.com> <41678287+gh05tn0va@users.noreply.github.com>
|
|
||||||
Zach G <zguan@vmware.com> zach96guan <zach96guan@users.noreply.github.com>
|
|
||||||
Zach Tucker <ztucker@vmware.com> <jzt@users.noreply.github.com>
|
|
||||||
Zee Yang <zeey@vmware.com> <zee.yang@gmail.com>
|
|
||||||
3515
vendor/github.com/vmware/govmomi/CHANGELOG.md
generated
vendored
3515
vendor/github.com/vmware/govmomi/CHANGELOG.md
generated
vendored
File diff suppressed because it is too large
Load Diff
197
vendor/github.com/vmware/govmomi/CONTRIBUTING.md
generated
vendored
197
vendor/github.com/vmware/govmomi/CONTRIBUTING.md
generated
vendored
@@ -1,197 +0,0 @@
|
|||||||
# Contributing to `govmomi`
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
First, fork the repository on GitHub to your personal account.
|
|
||||||
|
|
||||||
Change `$USER` in the examples below to your Github username if they are not the
|
|
||||||
same.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/vmware/govmomi.git && cd govmomi
|
|
||||||
|
|
||||||
# prevent accidentally pushing to vmware/govmomi
|
|
||||||
git config push.default nothing
|
|
||||||
git remote rename origin vmware
|
|
||||||
|
|
||||||
# add your fork
|
|
||||||
git remote add $USER git@github.com:$USER/govmomi.git
|
|
||||||
|
|
||||||
git fetch -av
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contribution Flow
|
|
||||||
|
|
||||||
This is a rough outline of what a contributor's workflow looks like:
|
|
||||||
|
|
||||||
- Create an issue describing the feature/fix
|
|
||||||
- Create a topic branch from where you want to base your work.
|
|
||||||
- Make commits of logical units.
|
|
||||||
- Make sure your commit messages are in the proper format (see below).
|
|
||||||
- Push your changes to a topic branch in your fork of the repository.
|
|
||||||
- Submit a pull request to `vmware/govmomi`.
|
|
||||||
|
|
||||||
See [below](#format-of-the-commit-message) for details on commit best practices
|
|
||||||
and **supported prefixes**, e.g. `govc: <message>`.
|
|
||||||
|
|
||||||
> **Note:** If you are new to Git(hub) check out [Git rebase, squash...oh
|
|
||||||
> my!](https://www.mgasch.com/2021/05/git-basics/) for more details on how to
|
|
||||||
> successfully contribute to an open source project.
|
|
||||||
|
|
||||||
### Example 1 - Fix a Bug in `govmomi`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b issue-<number> vmware/master
|
|
||||||
git add <files>
|
|
||||||
git commit -m "fix: ..." -m "Closes: #<issue-number>"
|
|
||||||
git push $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2 - Add a new (non-breaking) API to `govmomi`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b issue-<number> vmware/master
|
|
||||||
git add <files>
|
|
||||||
git commit -m "Add API ..." -m "Closes: #<issue-number>"
|
|
||||||
git push $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 3 - Add a Feature to `govc`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b issue-<number> vmware/master
|
|
||||||
git add <files>
|
|
||||||
git commit -m "govc: Add feature ..." -m "Closes: #<issue-number>"
|
|
||||||
git push $USER issue-<number>
|
|
||||||
```
|
|
||||||
**Note**:
|
|
||||||
To register the new `govc` command package, add a blank `_` import to `govmomi/govc/main.go`.
|
|
||||||
|
|
||||||
### Example 4 - Fix a Bug in `vcsim`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b issue-<number> vmware/master
|
|
||||||
git add <files>
|
|
||||||
git commit -m "vcsim: Fix ..." -m "Closes: #<issue-number>"
|
|
||||||
git push $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 5 - Document Breaking (API) Changes
|
|
||||||
|
|
||||||
Breaking changes, e.g. to the `govmomi` APIs, are highlighted in the `CHANGELOG`
|
|
||||||
and release notes when the keyword `BREAKING:` is used in the commit message
|
|
||||||
body.
|
|
||||||
|
|
||||||
The text after `BREAKING:` is used in the corresponding highlighted section.
|
|
||||||
Thus these details should be stated at the body of the commit message.
|
|
||||||
Multi-line strings are supported.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b issue-<number> vmware/master
|
|
||||||
git add <files>
|
|
||||||
cat << EOF | git commit -F -
|
|
||||||
Add ctx to funcXYZ
|
|
||||||
|
|
||||||
This commit introduces context.Context to function XYZ
|
|
||||||
Closes: #1234
|
|
||||||
|
|
||||||
BREAKING: Add ctx to funcXYZ()
|
|
||||||
EOF
|
|
||||||
|
|
||||||
git push $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stay in sync with Upstream
|
|
||||||
|
|
||||||
When your branch gets out of sync with the vmware/master branch, use the
|
|
||||||
following to update (rebase):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout issue-<number>
|
|
||||||
git fetch -a
|
|
||||||
git rebase vmware/master
|
|
||||||
git push --force-with-lease $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating Pull Requests
|
|
||||||
|
|
||||||
If your PR fails to pass CI or needs changes based on code review, it's ok to
|
|
||||||
add more commits stating the changes made, e.g. "Address review comments". This
|
|
||||||
is to assist the reviewer(s) to easily detect and review the recent changes.
|
|
||||||
|
|
||||||
In case of small PRs, it's ok to squash and force-push (see further below)
|
|
||||||
directly instead.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# incorporate review feedback
|
|
||||||
git add .
|
|
||||||
|
|
||||||
# create a fixup commit which will be merged into your (original) <commit>
|
|
||||||
git commit --fixup <commit>
|
|
||||||
git push $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
Be sure to add a comment to the PR indicating your new changes are ready to
|
|
||||||
review, as Github does not generate a notification when you git push.
|
|
||||||
|
|
||||||
Once the review is complete, squash and push your final commit(s):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# squash all commits into one
|
|
||||||
# --autosquash will automatically detect and merge fixup commits
|
|
||||||
git rebase -i --autosquash vmware/master
|
|
||||||
git push --force-with-lease $USER issue-<number>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
|
|
||||||
The coding style suggested by the Go community is used in `govmomi`. See the
|
|
||||||
[style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details.
|
|
||||||
|
|
||||||
Try to limit column width to 120 characters for both code and markdown documents
|
|
||||||
such as this one.
|
|
||||||
|
|
||||||
### Format of the Commit Message
|
|
||||||
|
|
||||||
We follow the conventions described in [How to Write a Git Commit
|
|
||||||
Message](http://chris.beams.io/posts/git-commit/).
|
|
||||||
|
|
||||||
Be sure to include any related GitHub issue references in the commit message,
|
|
||||||
e.g. `Closes: #<number>`.
|
|
||||||
|
|
||||||
The [`CHANGELOG.md`](./CHANGELOG.md) and release page uses **commit message
|
|
||||||
prefixes** for grouping and highlighting. A commit message that
|
|
||||||
starts with `[prefix:] ` will place this commit under the respective
|
|
||||||
section in the `CHANGELOG`.
|
|
||||||
|
|
||||||
The following example creates a commit referencing the `issue: 1234` and puts
|
|
||||||
the commit message in the `govc` `CHANGELOG` section:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit -s -m "govc: Add CLI command X" -m "Closes: #1234"
|
|
||||||
```
|
|
||||||
|
|
||||||
Currently the following prefixes are used:
|
|
||||||
|
|
||||||
- `api:` - Use for API-related changes
|
|
||||||
- `govc:` - Use for changes to `govc` CLI
|
|
||||||
- `vcsim:` - Use for changes to vCenter Simulator
|
|
||||||
- `chore:` - Use for repository related activities
|
|
||||||
- `fix:` - Use for bug fixes
|
|
||||||
- `docs:` - Use for changes to the documentation
|
|
||||||
- `examples:` - Use for changes to examples
|
|
||||||
|
|
||||||
If your contribution falls into multiple categories, e.g. `api` and `vcsim` it
|
|
||||||
is recommended to break up your commits using distinct prefixes.
|
|
||||||
|
|
||||||
### Running CI Checks and Tests
|
|
||||||
You can run both `make check` and `make test` from the top level of the
|
|
||||||
repository.
|
|
||||||
|
|
||||||
While `make check` will catch formatting and import errors, it will not apply
|
|
||||||
any fixes. The developer is expected to do that.
|
|
||||||
|
|
||||||
## Reporting Bugs and Creating Issues
|
|
||||||
|
|
||||||
When opening a new issue, try to roughly follow the commit message format
|
|
||||||
conventions above.
|
|
||||||
256
vendor/github.com/vmware/govmomi/CONTRIBUTORS
generated
vendored
256
vendor/github.com/vmware/govmomi/CONTRIBUTORS
generated
vendored
@@ -1,256 +0,0 @@
|
|||||||
# People who can (and typically have) contributed to this repository.
|
|
||||||
#
|
|
||||||
# This script is generated by contributors.sh
|
|
||||||
#
|
|
||||||
|
|
||||||
Abhijeet Kasurde <akasurde@redhat.com>
|
|
||||||
abrarshivani <abrarshivani@users.noreply.github.com>
|
|
||||||
Adam Chalkley <atc0005@users.noreply.github.com>
|
|
||||||
Adam Fowler <adam@adamfowler.org>
|
|
||||||
Adam Shannon <adamkshannon@gmail.com>
|
|
||||||
Akanksha Panse <pansea@vmware.com>
|
|
||||||
Al Biheiri <abiheiri@apple.com>
|
|
||||||
Alessandro Cortiana <alessandro.cortiana@gmail.com>
|
|
||||||
Alex <puzo2002@gmail.com>
|
|
||||||
Alex Bozhenko <alexbozhenko@fb.com>
|
|
||||||
Alex Ellis (VMware) <alexellis2@gmail.com>
|
|
||||||
Aligator <8278538+yet-another-aligator@users.noreply.github.com>
|
|
||||||
Alvaro Miranda <kikitux@gmail.com>
|
|
||||||
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br>
|
|
||||||
amanpaha <amanpahariya@microsoft.com>
|
|
||||||
Amit Bathla <abathla@.vmware.com>
|
|
||||||
amit bezalel <amit.bezalel@hpe.com>
|
|
||||||
Andrew <AndrewDi@users.noreply.github.com>
|
|
||||||
Andrew Chin <andrew@andrewtchin.com>
|
|
||||||
Andrew Kutz <akutz@vmware.com>
|
|
||||||
Andrey Klimentyev <andrey.klimentyev@flant.com>
|
|
||||||
Anfernee Yongkun Gui <agui@vmware.com>
|
|
||||||
angystardust <angystardust@users.noreply.github.com>
|
|
||||||
aniketGslab <aniket.shinde@gslab.com>
|
|
||||||
Ankit Vaidya <vaidyaa@vmware.com>
|
|
||||||
Ankur Huralikoppi <huralikoppia@vmware.com>
|
|
||||||
Anna Carrigan <anna.carrigan@hpe.com>
|
|
||||||
Antony Saba <awsaba@gmail.com>
|
|
||||||
Ariel Chinn <arielchinn@gmail.com>
|
|
||||||
Arran Walker <arran.walker@zopa.com>
|
|
||||||
Artem Anisimov <aanisimov@inbox.ru>
|
|
||||||
Arunesh Pandey <parunesh@vmware.com>
|
|
||||||
Aryeh Weinreb <aryehweinreb@gmail.com>
|
|
||||||
Augy StClair <augy@google.com>
|
|
||||||
Austin Parker <aparker@apprenda.com>
|
|
||||||
Balu Dontu <bdontu@vmware.com>
|
|
||||||
bastienbc <bastien.barbe.creuly@gmail.com>
|
|
||||||
Ben Corrie <bcorrie@vmware.com>
|
|
||||||
Ben Vickers <bvickers@pivotal.io>
|
|
||||||
Benjamin Davini <davinib@vmware.com>
|
|
||||||
Benjamin Peterson <benjamin@python.org>
|
|
||||||
Benjamin Vickers <bvickers@vmware.com>
|
|
||||||
Bhavya Choudhary <bhavyac@vmware.com>
|
|
||||||
Bob Killen <killen.bob@gmail.com>
|
|
||||||
Brad Fitzpatrick <bradfitz@golang.org>
|
|
||||||
Brian Rak <brak@vmware.com>
|
|
||||||
brian57860 <brian57860@users.noreply.github.com>
|
|
||||||
Bruce Downs <bruceadowns@gmail.com>
|
|
||||||
Bryan Venteicher <bryanventeicher@gmail.com>
|
|
||||||
Cédric Blomart <cblomart@gmail.com>
|
|
||||||
Cheng Cheng <chengch@vmware.com>
|
|
||||||
Chethan Venkatesh <chethanv@vmware.com>
|
|
||||||
Choudhury Sarada Prasanna Nanda <cspn@google.com>
|
|
||||||
Chris Marchesi <chrism@vancluevertech.com>
|
|
||||||
Christian Höltje <docwhat@gerf.org>
|
|
||||||
Clint Greenwood <cgreenwood@vmware.com>
|
|
||||||
cpiment <pimentel.carlos@gmail.com>
|
|
||||||
CuiHaozhi <cuihaozhi@chinacloud.com.cn>
|
|
||||||
Dan Ilan <danilan@google.com>
|
|
||||||
Dan Norris <protochron@users.noreply.github.com>
|
|
||||||
Daniel Frederick Crisman <daniel@crisman.org>
|
|
||||||
Daniel Mueller <deso@posteo.net>
|
|
||||||
Danny Lockard <danny.lockard@banno.com>
|
|
||||||
Dave Gress <gressd@vmware.com>
|
|
||||||
Dave Smith-Uchida <dsmithuchida@vmware.com>
|
|
||||||
Dave Tucker <dave@dtucker.co.uk>
|
|
||||||
David Gress <gressd@vmware.com>
|
|
||||||
David Stark <dave@davidstark.name>
|
|
||||||
Davide Agnello <dagnello@hp.com>
|
|
||||||
Davinder Kumar <davinderk@vmware.com>
|
|
||||||
Defa <zhoudefa666@163.com>
|
|
||||||
demarey <christophe.demarey@inria.fr>
|
|
||||||
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
|
||||||
Deric Crago <deric.crago@gmail.com>
|
|
||||||
ditsuke <ditsuke@protonmail.com>
|
|
||||||
Divyen Patel <divyenp@vmware.com>
|
|
||||||
Dnyanesh Gate <dnyanesh.gate@druva.com>
|
|
||||||
Doug MacEachern <dougm@vmware.com>
|
|
||||||
East <60801291+houfangdong@users.noreply.github.com>
|
|
||||||
Eloy Coto <eloy.coto@gmail.com>
|
|
||||||
embano1 <embano1@users.noreply.github.com>
|
|
||||||
Eng Zer Jun <engzerjun@gmail.com>
|
|
||||||
Eric Edens <ericedens@google.com>
|
|
||||||
Eric Graham <16710890+Pheric@users.noreply.github.com>
|
|
||||||
Eric Gray <egray@vmware.com>
|
|
||||||
Eric Yutao <eric.yutao@gmail.com>
|
|
||||||
Erik Hollensbe <github@hollensbe.org>
|
|
||||||
Essodjolo KAHANAM <essodjolo@kahanam.com>
|
|
||||||
Ethan Kaley <ethan.kaley@emc.com>
|
|
||||||
Evan Chu <echu@vmware.com>
|
|
||||||
Fabio Rapposelli <fabio@vmware.com>
|
|
||||||
Faiyaz Ahmed <faiyaza@vmware.com>
|
|
||||||
Federico Pellegatta <12744504+federico-pellegatta@users.noreply.github.com>
|
|
||||||
forkbomber <forkbomber@users.noreply.github.com>
|
|
||||||
François Rigault <rigault.francois@gmail.com>
|
|
||||||
freebsdly <qinhuajun@outlook.com>
|
|
||||||
Gavin Gray <gavin@infinio.com>
|
|
||||||
Gavrie Philipson <gavrie.philipson@elastifile.com>
|
|
||||||
George Hicken <ghicken@vmware.com>
|
|
||||||
Gerrit Renker <Gerrit.Renker@ctl.io>
|
|
||||||
gthombare <gthombare@vmware.com>
|
|
||||||
HakanSunay <hakansunay@abv.bg>
|
|
||||||
Hasan Mahmood <mahmoodh@vmware.com>
|
|
||||||
Haydon Ryan <haydon.ryan@gmail.com>
|
|
||||||
Heiko Reese <hreese@users.noreply.github.com>
|
|
||||||
Henrik Hodne <henrik@travis-ci.com>
|
|
||||||
hkumar <hkumar@vmware.com>
|
|
||||||
Hrabur Stoyanov <hstoyanov@vmware.com>
|
|
||||||
hui luo <luoh@vmware.com>
|
|
||||||
Ian Eyberg <ian@deferpanic.com>
|
|
||||||
Isaac Rodman <isaac@eyz.us>
|
|
||||||
Ivan Mikushin <imikushin@vmware.com>
|
|
||||||
Ivan Porto Carrero <icarrero@vmware.com>
|
|
||||||
James King <james.king@emc.com>
|
|
||||||
James Peach <jpeach@vmware.com>
|
|
||||||
Jason Kincl <jkincl@gmail.com>
|
|
||||||
Jeremy Canady <jcanady@jackhenry.com>
|
|
||||||
jeremy-clerc <jeremy@clerc.io>
|
|
||||||
Jiatong Wang <wjiatong@vmware.com>
|
|
||||||
jingyizPensando <jingyiz@pensando.io>
|
|
||||||
João Pereira <joaodrp@gmail.com>
|
|
||||||
Jonas Ausevicius <jonas.ausevicius@virtustream.com>
|
|
||||||
Jorge Sevilla <jorge.sevilla@rstor.io>
|
|
||||||
Julien PILLON <jpillon@lesalternatives.org>
|
|
||||||
Justin J. Novack <jnovack@users.noreply.github.com>
|
|
||||||
kayrus <kay.diam@gmail.com>
|
|
||||||
Keenan Brock <keenan@thebrocks.net>
|
|
||||||
Kevin George <georgek@vmware.com>
|
|
||||||
Knappek <andy.knapp.ak@gmail.com>
|
|
||||||
Leslie Wang <qiwa@pensando.io>
|
|
||||||
leslie-qiwa <leslie.qiwa@gmail.com>
|
|
||||||
Lintong Jiang <lintongj@vmware.com>
|
|
||||||
Liping Xue <lipingx@vmware.com>
|
|
||||||
Louie Jiang <jiangl@vmware.com>
|
|
||||||
Luther Monson <luther.monson@gmail.com>
|
|
||||||
Madanagopal Arunachalam <marunachalam@vmware.com>
|
|
||||||
makelarisjr <8687447+makelarisjr@users.noreply.github.com>
|
|
||||||
maplain <fangyuanl@vmware.com>
|
|
||||||
Marc Carmier <mcarmier@gmail.com>
|
|
||||||
Marcus Tan <marcus.tan@rubrik.com>
|
|
||||||
Maria Ntalla <maria.ntalla@gmail.com>
|
|
||||||
Marin Atanasov Nikolov <mnikolov@vmware.com>
|
|
||||||
Mario Trangoni <mjtrangoni@gmail.com>
|
|
||||||
Mark Dechiaro <mdechiaro@users.noreply.github.com>
|
|
||||||
Mark Peek <markpeek@vmware.com>
|
|
||||||
Mark Rexwinkel <Mark.Rexwinkel@elekta.com>
|
|
||||||
martin <martin@catai.org>
|
|
||||||
Matt Clay <matt@mystile.com>
|
|
||||||
Matt Moore <mattmoor@vmware.com>
|
|
||||||
Matt Moriarity <matt@mattmoriarity.com>
|
|
||||||
Matthew Cosgrove <matthew.cosgrove@dell.com>
|
|
||||||
mbhadale <mbhadale@vmware.com>
|
|
||||||
Merlijn Sebrechts <merlijn.sebrechts@gmail.com>
|
|
||||||
Mevan Samaratunga <mevansam@gmail.com>
|
|
||||||
Michael Gasch <15986659+embano1@users.noreply.github.com>
|
|
||||||
Michael Gasch <mgasch@vmware.com>
|
|
||||||
Michal Jankowski <mjankowski@vmware.com>
|
|
||||||
Mike Schinkel <mike@newclarity.net>
|
|
||||||
Mincho Tonev <mtonev@vmware.com>
|
|
||||||
mingwei <mingwei@smartx.com>
|
|
||||||
Nicolas Lamirault <nicolas.lamirault@gmail.com>
|
|
||||||
Nikhil Kathare <nikhil.kathare@netapp.com>
|
|
||||||
Nikhil R Deshpande <ndeshpande@vmware.com>
|
|
||||||
Nikolas Grottendieck <git@nikolasgrottendieck.com>
|
|
||||||
Nils Elde <nils.elde@sscinc.com>
|
|
||||||
nirbhay <nirbhay.bagmar@nutanix.com>
|
|
||||||
Nobuhiro MIKI <nmiki@yahoo-corp.jp>
|
|
||||||
Om Kumar <om.kumar@hpe.com>
|
|
||||||
Omar Kohl <omarkohl@gmail.com>
|
|
||||||
Parham Alvani <parham.alvani@gmail.com>
|
|
||||||
Parveen Chahal <parkuma@microsoft.com>
|
|
||||||
Paul Martin <25058109+rawstorage@users.noreply.github.com>
|
|
||||||
Pierre Gronlier <pierre.gronlier@corp.ovh.com>
|
|
||||||
Pieter Noordhuis <pnoordhuis@vmware.com>
|
|
||||||
pradeepj <50135054+pradeep288@users.noreply.github.com>
|
|
||||||
Pranshu Jain <jpranshu@vmware.com>
|
|
||||||
prydin <prydin@vmware.com>
|
|
||||||
rconde01 <rconde01@hotmail.com>
|
|
||||||
rHermes <teodor_spaeren@riseup.net>
|
|
||||||
Rianto Wahyudi <rwahyudi@gmail.com>
|
|
||||||
Ricardo Katz <rkatz@vmware.com>
|
|
||||||
Robin Watkins <robwatkins@gmail.com>
|
|
||||||
Rowan Jacobs <rojacobs@pivotal.io>
|
|
||||||
Roy Ling <royling0024@gmail.com>
|
|
||||||
rsikdar <rsikdar@berkeley.edu>
|
|
||||||
runner.mei <runner.mei@gmail.com>
|
|
||||||
Ryan Johnson <johnsonryan@vmware.com>
|
|
||||||
S R Ashrith <sashrith@vmware.com>
|
|
||||||
S.Çağlar Onur <conur@vmware.com>
|
|
||||||
Saad Malik <saad@spectrocloud.com>
|
|
||||||
Sam Zhu <zhusa@zhusa-a02.vmware.com>
|
|
||||||
samzhu333 <45263849+samzhu333@users.noreply.github.com>
|
|
||||||
Sandeep Pissay Srinivasa Rao <ssrinivas@vmware.com>
|
|
||||||
Scott Holden <scott@nullops.io>
|
|
||||||
Sergey Ignatov <sergey.ignatov@jetbrains.com>
|
|
||||||
serokles <timbo.alexander@gmail.com>
|
|
||||||
shahra <shahra@vmware.com>
|
|
||||||
Shalini Bhaskara <sbhaskara@vmware.com>
|
|
||||||
Shaozhen Ding <dsz0111@gmail.com>
|
|
||||||
Shawn Neal <sneal@sneal.net>
|
|
||||||
shylasrinivas <sshyla@vmware.com>
|
|
||||||
sky-joker <sky.jokerxx@gmail.com>
|
|
||||||
smaftoul <samuel.maftoul@gmail.com>
|
|
||||||
smahadik <smahadik@vmware.com>
|
|
||||||
Sten Feldman <exile@chamber.ee>
|
|
||||||
Stepan Mazurov <smazurov@gmail.com>
|
|
||||||
Steve Purcell <steve@sanityinc.com>
|
|
||||||
Sudhindra Aithal <sudhiaithal@pensando.io>
|
|
||||||
SUMIT AGRAWAL <asumit@vmware.com>
|
|
||||||
Sunny Carter <sunny.carter@metaswitch.com>
|
|
||||||
syuparn <s.hello.spagetti@gmail.com>
|
|
||||||
Takaaki Furukawa <takaaki.frkw@gmail.com>
|
|
||||||
Tamas Eger <tamas.eger@bitrise.io>
|
|
||||||
Tanay Kothari <tkothari@vmware.com>
|
|
||||||
tanishi <tanishi503@gmail.com>
|
|
||||||
Ted Zlatanov <tzz@lifelogs.com>
|
|
||||||
Thad Craft <tcraft@pivotal.io>
|
|
||||||
Thibaut Ackermann <thibaut.ackermann@alcatel-lucent.com>
|
|
||||||
Tim McNamara <tim.mcnamara@canonical.com>
|
|
||||||
Tjeu Kayim <15987676+TjeuKayim@users.noreply.github.com>
|
|
||||||
Toomas Pelberg <toomas.pelberg@playtech.com>
|
|
||||||
Trevor Dawe <trevor.dawe@gmail.com>
|
|
||||||
tshihad <tshihad9@gmail.com>
|
|
||||||
Uwe Bessle <Uwe.Bessle@iteratec.de>
|
|
||||||
Vadim Egorov <vegorov@vmware.com>
|
|
||||||
Vikram Krishnamurthy <vikramkrishnamu@vmware.com>
|
|
||||||
volanja <volaaanja@gmail.com>
|
|
||||||
Volodymyr Bobyr <pupsua@gmail.com>
|
|
||||||
Waldek Maleska <w.maleska@gmail.com>
|
|
||||||
William Lam <wlam@vmware.com>
|
|
||||||
Witold Krecicki <wpk@culm.net>
|
|
||||||
xing-yang <xingyang105@gmail.com>
|
|
||||||
xinyanw409 <wxinyan@vmware.com>
|
|
||||||
Yang Yang <yangy@vmware.com>
|
|
||||||
yangxi <yangxi@vmware.com>
|
|
||||||
Yann Hodique <yhodique@google.com>
|
|
||||||
Yash Nitin Desai <desaiy@vmware.com>
|
|
||||||
Yassine TIJANI <ytijani@vmware.com>
|
|
||||||
Yi Jiang <yijiang@vmware.com>
|
|
||||||
yiyingy <yiyingy@vmware.com>
|
|
||||||
ykakarap <yuva2811@gmail.com>
|
|
||||||
Yogesh Sobale <6104071+ysobale@users.noreply.github.com>
|
|
||||||
Yue Yin <yueyin@yuyin-a01.vmware.com>
|
|
||||||
Yun Zhou <yunz@vmware.com>
|
|
||||||
Yuya Kusakabe <yuya.kusakabe@gmail.com>
|
|
||||||
Zach G <zguan@vmware.com>
|
|
||||||
Zach Tucker <ztucker@vmware.com>
|
|
||||||
Zacharias Taubert <zacharias.taubert@gmail.com>
|
|
||||||
Zee Yang <zeey@vmware.com>
|
|
||||||
zyuxin <zyuxin@vmware.com>
|
|
||||||
Кузаков Евгений <kuzakov@satel.org>
|
|
||||||
45
vendor/github.com/vmware/govmomi/Dockerfile.govc
generated
vendored
45
vendor/github.com/vmware/govmomi/Dockerfile.govc
generated
vendored
@@ -1,45 +0,0 @@
|
|||||||
# Create a builder container
|
|
||||||
# golang:1.18.0-buster amd64
|
|
||||||
FROM golang@sha256:7d39537344486528f8cdb3bd8adb98ab7f0f4236044b6944fed8631da35a4ce5 AS build
|
|
||||||
WORKDIR /go/src/app
|
|
||||||
|
|
||||||
# Create appuser to isolate potential vulnerabilities
|
|
||||||
# See https://stackoverflow.com/a/55757473/12429735
|
|
||||||
ENV USER=appuser
|
|
||||||
ENV UID=10001
|
|
||||||
RUN adduser \
|
|
||||||
--disabled-password \
|
|
||||||
--gecos "" \
|
|
||||||
--shell "/sbin/nologin" \
|
|
||||||
--no-create-home \
|
|
||||||
--uid "${UID}" \
|
|
||||||
"${USER}"
|
|
||||||
|
|
||||||
# Create a new tmp directory so no bad actors can manipulate it
|
|
||||||
RUN mkdir /temporary-tmp-directory && chmod 777 /temporary-tmp-directory
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Final stage
|
|
||||||
FROM scratch
|
|
||||||
|
|
||||||
# Allow container to use latest TLS certificates
|
|
||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
|
|
||||||
# Copy over appuser to run as non-root
|
|
||||||
COPY --from=build /etc/passwd /etc/passwd
|
|
||||||
COPY --from=build /etc/group /etc/group
|
|
||||||
|
|
||||||
# Copy over the /tmp directory for golang/os.TmpDir
|
|
||||||
COPY --chown=appuser --from=build /temporary-tmp-directory /tmp
|
|
||||||
|
|
||||||
# Copy application from external build
|
|
||||||
COPY govc /govc
|
|
||||||
|
|
||||||
# Run all commands as non-root
|
|
||||||
USER appuser:appuser
|
|
||||||
|
|
||||||
# session cache, etc
|
|
||||||
ENV GOVMOMI_HOME=/tmp
|
|
||||||
|
|
||||||
# Set CMD to application with container defaults
|
|
||||||
CMD ["/govc"]
|
|
||||||
47
vendor/github.com/vmware/govmomi/Dockerfile.vcsim
generated
vendored
47
vendor/github.com/vmware/govmomi/Dockerfile.vcsim
generated
vendored
@@ -1,47 +0,0 @@
|
|||||||
# Create a builder container
|
|
||||||
# golang:1.18.0-buster amd64
|
|
||||||
FROM golang@sha256:7d39537344486528f8cdb3bd8adb98ab7f0f4236044b6944fed8631da35a4ce5 AS build
|
|
||||||
WORKDIR /go/src/app
|
|
||||||
|
|
||||||
# Create appuser to isolate potential vulnerabilities
|
|
||||||
# See https://stackoverflow.com/a/55757473/12429735
|
|
||||||
ENV USER=appuser
|
|
||||||
ENV UID=10001
|
|
||||||
RUN adduser \
|
|
||||||
--disabled-password \
|
|
||||||
--gecos "" \
|
|
||||||
--home "/nonexistent" \
|
|
||||||
--shell "/sbin/nologin" \
|
|
||||||
--no-create-home \
|
|
||||||
--uid "${UID}" \
|
|
||||||
"${USER}"
|
|
||||||
|
|
||||||
# Create a new tmp directory so no bad actors can manipulate it
|
|
||||||
RUN mkdir /temporary-tmp-directory && chmod 777 /temporary-tmp-directory
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Final stage
|
|
||||||
FROM scratch
|
|
||||||
|
|
||||||
# Run all commands as non-root
|
|
||||||
USER appuser:appuser
|
|
||||||
|
|
||||||
# Allow container to use latest TLS certificates
|
|
||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
|
|
||||||
# Copy over appuser to run as non-root
|
|
||||||
COPY --from=build /etc/passwd /etc/passwd
|
|
||||||
COPY --from=build /etc/group /etc/group
|
|
||||||
|
|
||||||
# Copy over the /tmp directory for golang/os.TmpDir
|
|
||||||
COPY --chown=appuser --from=build /temporary-tmp-directory /tmp
|
|
||||||
|
|
||||||
# Expose application port
|
|
||||||
EXPOSE 8989
|
|
||||||
|
|
||||||
# Copy application from external build
|
|
||||||
COPY vcsim /vcsim
|
|
||||||
|
|
||||||
# Set entrypoint to application with container defaults
|
|
||||||
ENTRYPOINT [ "/vcsim" ]
|
|
||||||
CMD ["-l", "0.0.0.0:8989"]
|
|
||||||
202
vendor/github.com/vmware/govmomi/LICENSE.txt
generated
vendored
202
vendor/github.com/vmware/govmomi/LICENSE.txt
generated
vendored
@@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
158
vendor/github.com/vmware/govmomi/Makefile
generated
vendored
158
vendor/github.com/vmware/govmomi/Makefile
generated
vendored
@@ -1,158 +0,0 @@
|
|||||||
# Copyright (c) 2021 VMware, Inc. All Rights Reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
# If you update this file, please follow
|
|
||||||
# https://www.thapaliya.com/en/writings/well-documented-makefiles/
|
|
||||||
|
|
||||||
# Ensure Make is run with bash shell as some syntax below is bash-specific
|
|
||||||
SHELL := /usr/bin/env bash
|
|
||||||
|
|
||||||
# Print the help/usage when make is executed without any other arguments
|
|
||||||
.DEFAULT_GOAL := help
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Help
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help: ## Display usage
|
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make [target] \033[36m\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Locations and programs
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
# Directories
|
|
||||||
BIN_DIR := bin
|
|
||||||
TOOLS_DIR := hack/tools
|
|
||||||
TOOLS_BIN_DIR := $(TOOLS_DIR)/bin
|
|
||||||
|
|
||||||
# Tooling binaries
|
|
||||||
GO ?= $(shell command -v go 2>/dev/null)
|
|
||||||
GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Prerequisites
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
# Do not proceed unless the go binary is present.
|
|
||||||
ifeq (,$(strip $(GO)))
|
|
||||||
$(error The "go" program cannot be found)
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Linting and fixing linter errors
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: ## Run all the lint targets
|
|
||||||
$(MAKE) lint-go-full
|
|
||||||
|
|
||||||
GOLANGCI_LINT_FLAGS ?= --fast=true
|
|
||||||
.PHONY: lint-go
|
|
||||||
lint-go: $(GOLANGCI_LINT) ## Lint codebase
|
|
||||||
$(GOLANGCI_LINT) run -v $(GOLANGCI_LINT_FLAGS)
|
|
||||||
|
|
||||||
.PHONY: lint-go-full
|
|
||||||
lint-go-full: GOLANGCI_LINT_FLAGS = --fast=false
|
|
||||||
lint-go-full: lint-go ## Run slower linters to detect possible issues
|
|
||||||
|
|
||||||
.PHONY: fix
|
|
||||||
fix: GOLANGCI_LINT_FLAGS = --fast=false --fix
|
|
||||||
fix: lint-go ## Tries to fix errors reported by lint-go-full target
|
|
||||||
|
|
||||||
.PHONY: check
|
|
||||||
check: lint-go-full
|
|
||||||
check: ## Run linters
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Tooling Binaries
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
TOOLING_BINARIES := $(GOLANGCI_LINT)
|
|
||||||
tools: $(TOOLING_BINARIES) ## Build tooling binaries
|
|
||||||
.PHONY: $(TOOLING_BINARIES)
|
|
||||||
$(TOOLING_BINARIES):
|
|
||||||
cd $(TOOLS_DIR); make $(@F)
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Build / Install
|
|
||||||
## --------------------------------------
|
|
||||||
.PHONY: install
|
|
||||||
install: ## Install govc and vcsim
|
|
||||||
$(MAKE) -C govc install
|
|
||||||
$(MAKE) -C vcsim install
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Generate
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
.PHONY: mod
|
|
||||||
mod: ## Runs go mod tidy to validate modules
|
|
||||||
go mod tidy -v
|
|
||||||
|
|
||||||
.PHONY: mod-get
|
|
||||||
mod-get: ## Downloads and caches the modules
|
|
||||||
go mod download
|
|
||||||
|
|
||||||
.PHONY: doc
|
|
||||||
doc: install
|
|
||||||
doc: ## Generates govc USAGE.md
|
|
||||||
./govc/usage.sh > ./govc/USAGE.md
|
|
||||||
|
|
||||||
|
|
||||||
## --------------------------------------
|
|
||||||
## Tests
|
|
||||||
## --------------------------------------
|
|
||||||
|
|
||||||
# Test options
|
|
||||||
TEST_COUNT ?= 1
|
|
||||||
TEST_TIMEOUT ?= 5m
|
|
||||||
TEST_RACE_HISTORY_SIZE ?= 5
|
|
||||||
GORACE ?= history_size=$(TEST_RACE_HISTORY_SIZE)
|
|
||||||
|
|
||||||
ifeq (-count,$(findstring -count,$(TEST_OPTS)))
|
|
||||||
$(error Use TEST_COUNT to override this option)
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq (-race,$(findstring -race,$(TEST_OPTS)))
|
|
||||||
$(error The -race flag is enabled by default & cannot be specified in TEST_OPTS)
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq (-timeout,$(findstring -timeout,$(TEST_OPTS)))
|
|
||||||
$(error Use TEST_TIMEOUT to override this option)
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: go-test
|
|
||||||
go-test: ## Runs go unit tests with race detector enabled
|
|
||||||
GORACE=$(GORACE) $(GO) test \
|
|
||||||
-count $(TEST_COUNT) \
|
|
||||||
-race \
|
|
||||||
-timeout $(TEST_TIMEOUT) \
|
|
||||||
-v $(TEST_OPTS) \
|
|
||||||
./...
|
|
||||||
|
|
||||||
.PHONY: govc-test
|
|
||||||
govc-test: install
|
|
||||||
govc-test: ## Runs govc bats tests
|
|
||||||
./govc/test/images/update.sh
|
|
||||||
(cd govc/test && ./vendor/github.com/sstephenson/bats/libexec/bats -t .)
|
|
||||||
|
|
||||||
.PHONY: govc-test-sso
|
|
||||||
govc-test-sso: install
|
|
||||||
./govc/test/images/update.sh
|
|
||||||
(cd govc/test && SSO_BATS=1 ./vendor/github.com/sstephenson/bats/libexec/bats -t sso.bats)
|
|
||||||
|
|
||||||
.PHONY: govc-test-sso-assert-cert
|
|
||||||
govc-test-sso-assert-cert:
|
|
||||||
SSO_BATS_ASSERT_CERT=1 $(MAKE) govc-test-sso
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: go-test govc-test ## Runs go-test and govc-test
|
|
||||||
131
vendor/github.com/vmware/govmomi/README.md
generated
vendored
131
vendor/github.com/vmware/govmomi/README.md
generated
vendored
@@ -1,131 +0,0 @@
|
|||||||
<!-- markdownlint-disable first-line-h1 no-inline-html -->
|
|
||||||
|
|
||||||
[][ci-build]
|
|
||||||
[][ci-tests]
|
|
||||||
[][go-report-card]
|
|
||||||
[][latest-release]
|
|
||||||
[][go-reference]
|
|
||||||
[][go-version]
|
|
||||||
|
|
||||||
# govmomi
|
|
||||||
|
|
||||||
A Go library for interacting with VMware vSphere APIs (ESXi and/or vCenter Server).
|
|
||||||
|
|
||||||
In addition to the vSphere API client, this repository includes:
|
|
||||||
|
|
||||||
* [govc][govc] - vSphere CLI
|
|
||||||
* [vcsim][vcsim] - vSphere API mock framework
|
|
||||||
* [toolbox][toolbox] - VM guest tools framework
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
This library supports vCenter Server and ESXi versions following the [VMware Product Lifecycle Matrix][reference-lifecycle].
|
|
||||||
|
|
||||||
Product versions that are end of support may work, but are not officially supported.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
The APIs exposed by this library closely follow the API described in the [VMware vSphere API Reference Documentation][reference-api]. Refer to the documentation to become familiar with the upstream API.
|
|
||||||
|
|
||||||
The code in the `govmomi` package is a wrapper for the code that is generated from the vSphere API description. It primarily provides convenience functions for working with the vSphere API. See [godoc.org][reference-godoc] for documentation.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### govmomi (Package)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get -u github.com/vmware/govmomi
|
|
||||||
```
|
|
||||||
|
|
||||||
### Binaries and Docker Images for `govc` and `vcsim`
|
|
||||||
|
|
||||||
Installation instructions, released binaries, and Docker images are documented in the respective README files of [`govc`][govc] and [`vcsim`][vcsim].
|
|
||||||
|
|
||||||
## Discussion
|
|
||||||
|
|
||||||
The project encourages the community to collaborate using GitHub [issues][govmomi-github-issues], GitHub [discussions][govmomi-github-discussions], and [Slack][slack-channel].
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
> Access to Slack requires a free [VMware {code}][slack-join] developer program membership.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Changes to the API are subject to [semantic versioning][reference-semver].
|
|
||||||
|
|
||||||
Refer to the [CHANGELOG][govmomi-changelog] for version to version changes.
|
|
||||||
|
|
||||||
## Notable Projects Using govmomi
|
|
||||||
|
|
||||||
* [collectd-vsphere][project-travisci-collectd-vsphere]
|
|
||||||
* [Docker LinuxKit][project-docker-linuxKit]
|
|
||||||
* [Elastic Agent VMware vSphere integration][project-elastic-agent]
|
|
||||||
* [Gru][project-gru]
|
|
||||||
* [Juju][project-juju]
|
|
||||||
* [Jupiter Brain][project-travisci-jupiter-brain]
|
|
||||||
* [Kubernetes vSphere Cloud Provider][project-k8s-cloud-provider]
|
|
||||||
* [Kubernetes Cluster API][project-k8s-cluster-api]
|
|
||||||
* [OPS][project-nanovms-ops]
|
|
||||||
* [Packer Plugin for VMware vSphere][project-hashicorp-packer-plugin-vsphere]
|
|
||||||
* [Rancher][project-rancher]
|
|
||||||
* [Terraform Provider for VMware vSphere][project-hashicorp-terraform-provider-vsphere]
|
|
||||||
* [Telegraf][project-influxdata-telegraf]
|
|
||||||
* [VMware Event Broker Appliance][project-vmware-veba]
|
|
||||||
* [VMware vSphere Integrated Containers Engine][project-vmware-vic]
|
|
||||||
* [VMware vSphere 7.0][project-vmware-vsphere]
|
|
||||||
|
|
||||||
## Related Projects
|
|
||||||
|
|
||||||
* [go-vmware-nsxt][reference-go-vmware-nsxt]
|
|
||||||
* [pyvmomi][reference-pyvmomi]
|
|
||||||
* [rbvmomi][reference-rbvmomi]
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
govmomi is available under the [Apache 2 License][govmomi-license].
|
|
||||||
|
|
||||||
## Name
|
|
||||||
|
|
||||||
Pronounced: _go·v·mom·ie_
|
|
||||||
|
|
||||||
Follows pyvmomi and rbvmomi: language prefix + the vSphere acronym "VM Object Management Infrastructure".
|
|
||||||
|
|
||||||
[//]: Links
|
|
||||||
|
|
||||||
[ci-build]: https://github.com/vmware/govmomi/actions/workflows/govmomi-build.yaml
|
|
||||||
[ci-tests]: https://github.com/vmware/govmomi/actions/workflows/govmomi-go-tests.yaml
|
|
||||||
[latest-release]: https://github.com/vmware/govmomi/releases/latest
|
|
||||||
[govc]: govc/README.md
|
|
||||||
[govmomi-github-issues]: https://github.com/vmware/govmomi/issues
|
|
||||||
[govmomi-github-discussions]: https://github.com/vmware/govmomi/discussions
|
|
||||||
[govmomi-changelog]: CHANGELOG.md
|
|
||||||
[govmomi-license]: LICENSE.txt
|
|
||||||
[go-reference]: https://pkg.go.dev/github.com/vmware/govmomi
|
|
||||||
[go-report-card]: https://goreportcard.com/report/github.com/vmware/govmomi
|
|
||||||
[go-version]: https://github.com/vmware/govmomi
|
|
||||||
[project-docker-linuxKit]: https://github.com/linuxkit/linuxkit/tree/master/src/cmd/linuxkit
|
|
||||||
[project-elastic-agent]: https://github.com/elastic/integrations/tree/main/packages/vsphere
|
|
||||||
[project-gru]: https://github.com/dnaeon/gru
|
|
||||||
[project-hashicorp-packer-plugin-vsphere]: https://github.com/hashicorp/packer-plugin-vsphere
|
|
||||||
[project-hashicorp-terraform-provider-vsphere]: https://github.com/hashicorp/terraform-provider-vsphere
|
|
||||||
[project-influxdata-telegraf]: https://github.com/influxdata/telegraf/tree/master/plugins/inputs/vsphere
|
|
||||||
[project-juju]: https://github.com/juju/juju
|
|
||||||
[project-k8s-cloud-provider]: https://github.com/kubernetes/cloud-provider-vsphere
|
|
||||||
[project-k8s-cluster-api]: https://github.com/kubernetes-sigs/cluster-api-provider-vsphere
|
|
||||||
[project-nanovms-ops]: https://github.com/nanovms/ops
|
|
||||||
[project-rancher]: https://github.com/rancher/rancher/blob/master/pkg/api/norman/customization/vsphere/listers.go
|
|
||||||
[project-travisci-collectd-vsphere]: https://github.com/travis-ci/collectd-vsphere
|
|
||||||
[project-travisci-jupiter-brain]: https://github.com/travis-ci/jupiter-brain
|
|
||||||
[project-vmware-veba]: https://github.com/vmware-samples/vcenter-event-broker-appliance/tree/development/vmware-event-router
|
|
||||||
[project-vmware-vic]: https://github.com/vmware/vic
|
|
||||||
[project-vmware-vsphere]: https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-vcenter-server-7-vsphere-with-kubernetes-release-notes.html
|
|
||||||
[reference-api]: https://developer.vmware.com/apis/968/vsphere
|
|
||||||
[reference-godoc]: http://godoc.org/github.com/vmware/govmomi
|
|
||||||
[reference-go-vmware-nsxt]: https://github.com/vmware/go-vmware-nsxt
|
|
||||||
[reference-lifecycle]: https://lifecycle.vmware.com
|
|
||||||
[reference-pyvmomi]: https://github.com/vmware/pyvmomi
|
|
||||||
[reference-rbvmomi]: https://github.com/vmware/rbvmomi
|
|
||||||
[reference-semver]: http://semver.org
|
|
||||||
[slack-join]: https://developer.vmware.com/join/
|
|
||||||
[slack-channel]: https://vmwarecode.slack.com/messages/govmomi
|
|
||||||
[toolbox]: toolbox/README.md
|
|
||||||
[vcsim]: vcsim/README.md
|
|
||||||
225
vendor/github.com/vmware/govmomi/RELEASE.md
generated
vendored
225
vendor/github.com/vmware/govmomi/RELEASE.md
generated
vendored
@@ -1,225 +0,0 @@
|
|||||||
# How to create a `govmomi` Release on Github
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> The steps outlined in this document can only be performed by maintainers or
|
|
||||||
> administrators of this project.
|
|
||||||
|
|
||||||
The release automation is based on Github
|
|
||||||
[Actions](https://github.com/features/actions) and has been improved over time
|
|
||||||
to simplify the experience for creating `govmomi` releases.
|
|
||||||
|
|
||||||
The Github Actions release [workflow](.github/workflows/govmomi-release.yaml)
|
|
||||||
uses [`goreleaser`](http://goreleaser.com/) and automatically creates/pushes:
|
|
||||||
|
|
||||||
- Release artifacts for `govc` and `vcsim` to the
|
|
||||||
[release](https://github.com/vmware/govmomi/releases) page, including
|
|
||||||
`LICENSE.txt`, `README` and `CHANGELOG`
|
|
||||||
- Docker images for `vmware/govc` and `vmware/vcsim` to Docker Hub
|
|
||||||
- Source code
|
|
||||||
|
|
||||||
Starting with release tag `v0.29.0`, releases are not tagged on the `master`
|
|
||||||
branch anymore but a dedicated release branch, for example `release-0.29`. This
|
|
||||||
process has already been followed for patch releases and back-ports.
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> If you create a release after the `v0.29.0` tag, start
|
|
||||||
> [here](#creating-a-release-after-v0290). To create a release with an older
|
|
||||||
> tag, e.g. cherrypick or back-port, continue
|
|
||||||
> [here](#creating-a-release-before-v0290).
|
|
||||||
|
|
||||||
## Creating a release after Version `v0.29.0`
|
|
||||||
|
|
||||||
The release process from `v0.29.0` has been further simplified and is done
|
|
||||||
through the Github UI. The only pre-requirement is creating a release branch,
|
|
||||||
which can be done through the Github UI or `git` CLI.
|
|
||||||
|
|
||||||
This guide describes the CLI process.
|
|
||||||
|
|
||||||
### Verify `master` branch is up to date with the remote
|
|
||||||
|
|
||||||
```console
|
|
||||||
git checkout master
|
|
||||||
git fetch -avp
|
|
||||||
git diff master origin/master
|
|
||||||
|
|
||||||
# if your local and remote branches diverge run
|
|
||||||
git pull origin/master
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> These steps assume `origin` to point to the remote
|
|
||||||
> `https://github.com/vmware/govmomi`, respectively
|
|
||||||
> `git@github.com:vmware/govmomi`.
|
|
||||||
|
|
||||||
### Create a release branch
|
|
||||||
|
|
||||||
For new releases, create a release branch from the most recent commit in
|
|
||||||
`master`, e.g. `release-0.30`.
|
|
||||||
|
|
||||||
```console
|
|
||||||
export RELEASE_BRANCH=release-0.30
|
|
||||||
git checkout -b ${RELEASE_BRANCH}
|
|
||||||
```
|
|
||||||
|
|
||||||
For maintenance/patch releases on **existing** release branches **after** tag
|
|
||||||
`v0.29.0` simply checkout the existing release branch and add commits to the
|
|
||||||
existing release branch.
|
|
||||||
|
|
||||||
### Verify `make docs` and `CONTRIBUTORS` are up to date
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> Run the following commands and commit any changes to the release branch before
|
|
||||||
> proceeding with the release.
|
|
||||||
|
|
||||||
```console
|
|
||||||
make doc
|
|
||||||
./scripts/contributors.sh
|
|
||||||
if [ -z "$(git status --porcelain)" ]; then
|
|
||||||
echo "working directory clean: proceed with release"
|
|
||||||
else
|
|
||||||
echo "working directory dirty: please commit changes"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# perform git add && git commit ... in case there were changes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Push the release branch
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> Do not create a tag as this will be done by the release automation.
|
|
||||||
|
|
||||||
The final step is pushing the new/updated release branch.
|
|
||||||
|
|
||||||
```console
|
|
||||||
git push origin ${RELEASE_BRANCH}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create a release in the Github UI
|
|
||||||
|
|
||||||
Open the `govmomi` Github [repository](https://github.com/vmware/govmomi) and
|
|
||||||
navigate to `Actions -> Workflows -> Release`.
|
|
||||||
|
|
||||||
Click `Run Workflow` which opens a dropdown list.
|
|
||||||
|
|
||||||
Select the new/updated branch, e.g. `release-0.30`, i.e. **not** the `master`
|
|
||||||
branch.
|
|
||||||
|
|
||||||
Specify a semantic `tag` to associate with the release, e.g. `v0.30.0`.
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> This tag **must not** exist or the release will fail during the validation
|
|
||||||
> phase.
|
|
||||||
|
|
||||||
By default, a dry-run is performed to rule out most (but not all) errors during
|
|
||||||
a release. If you do not want to perform a dry-run, e.g. to finally create a
|
|
||||||
release, deselect the `Verify release workflow ...` checkbox.
|
|
||||||
|
|
||||||
Click `Run Workflow` to kick off the workflow.
|
|
||||||
|
|
||||||
After successful completion and if the newly created `tag` is the **latest**
|
|
||||||
(semantic version sorted) tag in the repository, a PR is automatically opened
|
|
||||||
against the `master` branch to update the `CHANGELOG`. Please review and merge
|
|
||||||
accordingly.
|
|
||||||
|
|
||||||
## Creating a release before Version `v0.29.0`
|
|
||||||
|
|
||||||
The release process before `v0.29.0` differs since it's based on manually
|
|
||||||
creating and pushing tags. Here, on every new tag matching `v*` pushed to the
|
|
||||||
repository a Github Action Release Workflow is executed.
|
|
||||||
|
|
||||||
### Verify `master` branch is up to date with the remote
|
|
||||||
|
|
||||||
```console
|
|
||||||
git checkout master
|
|
||||||
git fetch -avp
|
|
||||||
git diff master origin/master
|
|
||||||
|
|
||||||
# if your local and remote branches diverge run
|
|
||||||
git pull origin/master
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> These steps assume `origin` to point to the remote
|
|
||||||
> `https://github.com/vmware/govmomi`, respectively
|
|
||||||
> `git@github.com:vmware/govmomi`.
|
|
||||||
|
|
||||||
### Create a release branch
|
|
||||||
|
|
||||||
Pick a reference (commit, branch or tag) **before** the `v0.29.0` tag and create
|
|
||||||
a release branch from there.
|
|
||||||
|
|
||||||
The following example creates a cherrypick release (`v0.28.1`) based on the
|
|
||||||
`v0.28.0` tag.
|
|
||||||
|
|
||||||
```console
|
|
||||||
export RELEASE_BRANCH=release-0.28
|
|
||||||
git checkout -b ${RELEASE_BRANCH} v0.28.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Optionally, incorporate (cherry-pick) commits into the branch.
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> Make sure that these commits/ranges do not contain commits after the `v0.29.0`
|
|
||||||
> tag which include release automation changes, i.e. files in `.github/workflows/`!
|
|
||||||
|
|
||||||
### Verify `make docs` and `CONTRIBUTORS` are up to date
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> Run the following commands and commit any changes to the release branch before
|
|
||||||
> proceeding with the release.
|
|
||||||
|
|
||||||
```console
|
|
||||||
make doc
|
|
||||||
./scripts/contributors.sh
|
|
||||||
if [ -z "$(git status --porcelain)" ]; then
|
|
||||||
echo "working directory clean: proceed with release"
|
|
||||||
else
|
|
||||||
echo "working directory dirty: please commit changes"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# perform git add && git commit ... in case there were changes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Set `RELEASE_VERSION` variable
|
|
||||||
|
|
||||||
This variable is used and referenced in the subsequent commands. Set it to the
|
|
||||||
**upcoming** release version, adhering to the [semantic
|
|
||||||
versioning](https://semver.org/) scheme:
|
|
||||||
|
|
||||||
```console
|
|
||||||
export RELEASE_VERSION=v0.28.1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create the Git Tag
|
|
||||||
|
|
||||||
```console
|
|
||||||
git tag -a ${RELEASE_VERSION} -m "Release ${RELEASE_VERSION}"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Push the new Tag
|
|
||||||
|
|
||||||
```console
|
|
||||||
# Will trigger Github Actions Release Workflow
|
|
||||||
git push --atomic origin ${RELEASE_BRANCH} refs/tags/${RELEASE_VERSION}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verify Github Action Release Workflow
|
|
||||||
|
|
||||||
After pushing a new release tag, the status of the workflow can be inspected
|
|
||||||
[here](https://github.com/vmware/govmomi/actions/workflows/govmomi-release.yaml).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
After a successful release, a pull request is automatically created by the
|
|
||||||
Github Actions bot to update the [CHANGELOG](CHANGELOG.md). This `CHANGELOG.md`
|
|
||||||
is also generated with `git-chglog` but uses a slightly different template
|
|
||||||
(`.chglog/CHANGELOG.tpl.md`) for rendering (issue/PR refs are excluded).
|
|
||||||
136
vendor/github.com/vmware/govmomi/client.go
generated
vendored
136
vendor/github.com/vmware/govmomi/client.go
generated
vendored
@@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
This package is the root package of the govmomi library.
|
|
||||||
|
|
||||||
The library is structured as follows:
|
|
||||||
|
|
||||||
Package vim25
|
|
||||||
|
|
||||||
The minimal usable functionality is available through the vim25 package.
|
|
||||||
It contains subpackages that contain generated types, managed objects, and all
|
|
||||||
available methods. The vim25 package is entirely independent of the other
|
|
||||||
packages in the govmomi tree -- it has no dependencies on its peers.
|
|
||||||
|
|
||||||
The vim25 package itself contains a client structure that is
|
|
||||||
passed around throughout the entire library. It abstracts a session and its
|
|
||||||
immutable state. See the vim25 package for more information.
|
|
||||||
|
|
||||||
Package session
|
|
||||||
|
|
||||||
The session package contains an abstraction for the session manager that allows
|
|
||||||
a user to login and logout. It also provides access to the current session
|
|
||||||
(i.e. to determine if the user is in fact logged in)
|
|
||||||
|
|
||||||
Package object
|
|
||||||
|
|
||||||
The object package contains wrappers for a selection of managed objects. The
|
|
||||||
constructors of these objects all take a *vim25.Client, which they pass along
|
|
||||||
to derived objects, if applicable.
|
|
||||||
|
|
||||||
Package govc
|
|
||||||
|
|
||||||
The govc package contains the govc CLI. The code in this tree is not intended
|
|
||||||
to be used as a library. Any functionality that govc contains that _could_ be
|
|
||||||
used as a library function but isn't, _should_ live in a root level package.
|
|
||||||
|
|
||||||
Other packages
|
|
||||||
|
|
||||||
Other packages, such as "event", "guest", or "license", provide wrappers for
|
|
||||||
the respective subsystems. They are typically not needed in normal workflows so
|
|
||||||
are kept outside the object package.
|
|
||||||
*/
|
|
||||||
package govmomi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/vmware/govmomi/property"
|
|
||||||
"github.com/vmware/govmomi/session"
|
|
||||||
"github.com/vmware/govmomi/vim25"
|
|
||||||
"github.com/vmware/govmomi/vim25/soap"
|
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
*vim25.Client
|
|
||||||
|
|
||||||
SessionManager *session.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new client from a URL. The client authenticates with the
|
|
||||||
// server with username/password before returning if the URL contains user information.
|
|
||||||
func NewClient(ctx context.Context, u *url.URL, insecure bool) (*Client, error) {
|
|
||||||
soapClient := soap.NewClient(u, insecure)
|
|
||||||
vimClient, err := vim25.NewClient(ctx, soapClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Client{
|
|
||||||
Client: vimClient,
|
|
||||||
SessionManager: session.NewManager(vimClient),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only login if the URL contains user information.
|
|
||||||
if u.User != nil {
|
|
||||||
err = c.Login(ctx, u.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login dispatches to the SessionManager.
|
|
||||||
func (c *Client) Login(ctx context.Context, u *url.Userinfo) error {
|
|
||||||
return c.SessionManager.Login(ctx, u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout dispatches to the SessionManager.
|
|
||||||
func (c *Client) Logout(ctx context.Context) error {
|
|
||||||
// Close any idle connections after logging out.
|
|
||||||
defer c.Client.CloseIdleConnections()
|
|
||||||
return c.SessionManager.Logout(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PropertyCollector returns the session's default property collector.
|
|
||||||
func (c *Client) PropertyCollector() *property.Collector {
|
|
||||||
return property.DefaultCollector(c.Client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetrieveOne dispatches to the Retrieve function on the default property collector.
|
|
||||||
func (c *Client) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, p []string, dst interface{}) error {
|
|
||||||
return c.PropertyCollector().RetrieveOne(ctx, obj, p, dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve dispatches to the Retrieve function on the default property collector.
|
|
||||||
func (c *Client) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, p []string, dst interface{}) error {
|
|
||||||
return c.PropertyCollector().Retrieve(ctx, objs, p, dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait dispatches to property.Wait.
|
|
||||||
func (c *Client) Wait(ctx context.Context, obj types.ManagedObjectReference, ps []string, f func([]types.PropertyChange) bool) error {
|
|
||||||
return property.Wait(ctx, c.PropertyCollector(), obj, ps, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsVC returns true if we are connected to a vCenter
|
|
||||||
func (c *Client) IsVC() bool {
|
|
||||||
return c.Client.IsVC()
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user