350 Commits
v3.8 ... v4.1.4

Author SHA1 Message Date
Doug Smith
4fc16b3bb8 Merge pull request #1355 from Nordix/fix-cve-moshiur
Support go 1.22 and 1.23 build to fix CVE
2024-12-06 16:56:48 -05:00
smoshiur1237
ddbcd2c4ef Support go 1.22 to fix CVE
Signed-off-by: smoshiur1237 <moshiur.rahman@est.tech>
2024-12-05 17:03:03 +02:00
Tomofumi Hayashi
781ecdaecd Merge pull request #1353 from xrstf/master
clean up go.mod, get rid of client-go v1.5.2
2024-11-08 01:53:15 +09:00
Christoph Mewes
808185b10f clean up go.mod, get rid of client-go v1.5.2 2024-11-06 12:51:30 +01:00
Doug Smith
e1a0d2a3fd Merge pull request #1345 from dougbtv/net-attach-def-lib-175
Update net-attach-def client library to 1.7.5 for cri-o functionality
2024-10-15 12:05:24 -04:00
dougbtv
ecf5854ca9 Update net-attach-def client library to 1.7.5 for cri-o functionality
From the release notes:

> This release contains a fix related to the determination of the default interface, e.g. setting the default parameter to true in the network-status annotation based on the presence of a gateway in the CNI ADD success result ips.gateway and makes the determination of the default based on the first interface that has an associated value of gateway (using the interface index in the ips element in the CNI ADD success result).

> This provides flexibility especially in CRI-O which uses the first interface and IP addresses for the pod.IP in Kubernetes, therefore. Containerd functionality is unchanged in that it uses the value for the IP addresses specifically

> It's worth noting that CNI ADD success results which do not contain any interfaces will be discarded in this determination of the default, therefore it's recommended to set one with an associated gateway if aiming to have it be noted as the default.

See also:
https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/releases/tag/v1.7.5
https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/pull/73
2024-10-15 11:37:32 -04:00
Doug Smith
adfb270991 Merge pull request #1341 from dougbtv/net-attach-def-client-v174
Updates net-attach-def client library to v1.7.4
2024-10-01 09:36:43 -04:00
dougbtv
b171bb702b Updates net-attach-def client library to v1.7.4
Which improves backwards compatibility for network-status in latest updates to the client library, especially related to Calico.

See also: https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/pull/72
2024-09-30 15:57:48 -04:00
Doug Smith
f1e887e239 Merge pull request #1336 from dougbtv/net-attach-def-client-v173
Bumps net-attach-def client to v1.7.3
2024-09-13 12:03:57 -04:00
dougbtv
100766d1a4 Bumps net-attach-def client to v1.7.3
Previous version didn't account for accounts for the sandox interfaces when reporting the interfaces in the network-status annotation when calculating the default:true interface
2024-09-13 09:56:52 -04:00
Doug Smith
e074c2a56b Merge pull request #1335 from dougbtv/net-attach-def-client-v172
Bumps net-attach-def client library to v1.7.2
2024-09-12 13:35:46 -04:00
dougbtv
38d03eb816 Bumps net-attach-def client library to v1.7.2
This fixes the default:true for multiple interface returns from CNI for cluster default network, where all interfaces in that return were marked as default:true in the network-status
2024-09-12 11:25:26 -04:00
Doug Smith
b554c96160 Merge pull request #1334 from dougbtv/disable-dra-e2e-temporarily
Disabled DRA test temporarily
2024-09-12 10:55:16 -04:00
dougbtv
92ff1b1ee8 Disabled DRA test temporarily 2024-09-12 10:06:17 -04:00
Doug Smith
31e77aafab Merge pull request #1321 from ah8ad3/update-install-readme
Doc: change install from file to url in readme, how-to-use
2024-08-29 09:41:23 -04:00
Ahmad Zolfaghari
dec0607a94 doc: change install from file to url in readme, how-to-use
Signed-off-by: Ahmad Zolfaghari <ah8ad3@gmail.com>
2024-08-09 16:32:07 +03:30
Doug Smith
3c33f6f028 Merge pull request #1314 from dougbtv/client-lib-multiple-if-cni-result
Updates to use CreateNetworkStauses from net-attach-def client, bump to v1.7.1
2024-08-05 07:59:36 -04:00
dougbtv
a28d1e4693 [bump] Bumps net-attach-def client lib to v1.7.1 2024-08-01 13:35:34 -04:00
Doug Smith
b9019016d7 Merge pull request #1317 from adrianchiris/pr-1302-update
create multus kubeconfig for in case of non auto flag.
2024-08-01 13:29:00 -04:00
abasitt
ca21ef66d1 create multus kubeconfig for incase of non auto flag. 2024-08-01 18:25:28 +03:00
Doug Smith
b11ea828e9 Merge pull request #1299 from adrianchiris/cleanup-multus-conf
Cleanup multus conf on exit
2024-08-01 09:37:25 -04:00
Doug Smith
fd9736b527 Merge pull request #1297 from sfackler/thick-arm
Build multiarch thick images
2024-08-01 09:35:58 -04:00
adrianc
6ade0ce262 update how-to-use doc
Signed-off-by: adrianc <adrianc@nvidia.com>
2024-08-01 16:18:55 +03:00
Doug Smith
f54693f501 Merge pull request #1313 from vasrem/chore/update-dra-e2e
Update DRA integration info and fix e2e test
2024-07-25 16:28:32 -04:00
dougbtv
bc6c8d5c76 Updates to use CreateNetworkStauses from net-attach-def client for multiple interfaces in CNI results 2024-07-25 16:15:50 -04:00
Vasilis Remmas
04fb8190fe Update DRA integration info and e2e test
Signed-off-by: Vasilis Remmas <vremmas@nvidia.com>
2024-07-24 09:34:53 +02:00
adrianc
004f1e6a12 delete multus conf file generated by thin_entrypoint
- if cleanup-config-on-exit is set delete generated multus
  config file on exit.

- add an option to skip watch for master cni config and kubeconfig
  as cleanup-config-on-exit with multus-conf-file=auto also triggered
  the watch for cases when deletion of multus config is desired but watch isnt

- setup signal handling to allow config file cleanup on exit

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-07-18 18:09:23 +03:00
adrianc
334fdce751 Add signals package
this provides a simple way to handle incoming
os signas using context

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-07-18 18:09:22 +03:00
Doug Smith
8587734174 Merge pull request #1305 from alrokayan/patch-1
Update how-to-use.md
2024-07-18 10:15:49 -04:00
Mohammed Alrokayan
9b4a0ce9a6 Update how-to-use.md
typo KUBERNETES_SERVICE_PROTOCOL
2024-06-30 15:20:10 +03:00
Steven Fackler
cd81346c1a Add a note about multi-arch support 2024-06-25 11:39:21 -04:00
Doug Smith
41013e7580 Merge pull request #1300 from adrianchiris/fix-mount-prop-var-lib-kubelet
Add mountPropagation to host-var-lib-kubelet
2024-06-20 09:50:47 -04:00
adrianc
849a3cd453 Add mountPropagation to host-var-lib-kubelet
This is needed to avoid issues with volume unmounts
when (other) pods exit as by default kubernetes mounts
as private.

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-06-18 16:25:18 +03:00
Steven Fackler
572e4e05e3 Build multiarch thick images 2024-06-12 12:46:29 -04:00
Doug Smith
aff99fccc5 Merge pull request #1284 from ader1990/patch-1
arm64: build image for arm v8
2024-06-06 09:57:43 -04:00
Doug Smith
4757be12ac Merge pull request #1286 from s1061123/fix-type-scope-comment
Not exposed APIReadyCheckFunc to outside of package
2024-06-06 09:55:42 -04:00
Tomofumi Hayashi
d23856b784 Not exposed APIReadyCheckFunc to outside of package
APIReadyCheckFunc is used only in api, hence it can be decapitalize
to make its scope only in this package. This fix changes its scope.
In addition, api.APIReadyCheckFunc seems to be redundant so the name
is changed. Change the comment to fit to golang style, too.
2024-05-25 01:40:12 +09:00
Doug Smith
9f5c0239a8 Merge pull request #1078 from moshe010/dra
add support for Dynamic Resource Allocation
2024-05-23 11:06:17 -04:00
Doug Smith
d9f1c7c6e7 Merge pull request #1243 from adrianchiris/allow_undersocre_in_ifname
Change Validation of interface name
2024-05-23 09:43:12 -04:00
Adrian Vladu
bb47b55999 arm64: build image for arm v8
Fixes: https://github.com/k8snetworkplumbingwg/multus-cni/issues/1281

Signed-off-by: Adrian Vladu <avladu@cloudbasesolutions.com>
2024-05-17 16:59:50 +03:00
Tomofumi Hayashi
75c0245020 Merge pull request #1279 from dougbtv/skip-api-ready-on-cni-del
Thick plugin should not wait for API readiness on CNI DEL
2024-05-15 22:50:50 +09:00
dougbtv
181f56f026 Thick plugin should not wait for API readiness on CNI DEL
This modifies the behavior on CNI DEL for the thick plugin to just check once for API readiness, as opposed to waiting.
2024-05-14 11:23:47 -04:00
Vasilis Remmas
c9d411c2c2 Add warning in docs that DRA is alpha and in preview
Signed-off-by: Vasilis Remmas <vremmas@nvidia.com>
2024-05-13 13:49:59 +02:00
Tomofumi Hayashi
5a2597b329 Merge pull request #1270 from ordovicia/thin-master-cni-file-flag
thin plugin: Handle `--multus-master-cni-file-name` flag
2024-05-10 01:12:15 +09:00
Doug Smith
c6a371b6bc Merge pull request #1274 from s1061123/fix/gateway-nil
Fix CNI cache update function to prevent nil access
2024-05-09 23:22:23 +09:00
Tomofumi Hayashi
5fe124932a Fix CNI cache update function to prevent nil access
deleteDefaultGWResult() may create 'routes:null' in CNI cache file
and it causes nil pointer access at addDefaultGWCacheBytes().
This code change prevents deleteDefaultGWResult() to generate
'routes:null' in cache file.
2024-05-09 04:03:00 +09:00
Tomofumi Hayashi
4457289d1d Merge pull request #1272 from s1061123/fix-readiness
Fix defaultnetworkfile in unit test
2024-05-02 02:39:17 +09:00
Tomofumi Hayashi
541a8032c3 Fix defaultnetworkfile in unit test
rename conf param, 'defaultnetworkfile' to 'readinessindicatorfile'
2024-05-02 02:30:26 +09:00
Tomofumi Hayashi
89188023ef Merge pull request #1266 from k8snetworkplumbingwg/dependabot/go_modules/golang.org/x/net-0.23.0
Bump golang.org/x/net from 0.17.0 to 0.23.0
2024-05-01 15:29:20 +09:00
Hidehito Yabuuchi
633985d82f thin plugin: Handle --multus-master-cni-file-name flag
Multus v3.9.3 has `--multus-master-cni-file-name` flag to specify the
name of a primary CNI config file.
https://github.com/k8snetworkplumbingwg/multus-cni/blob/v3.9.3/images/entrypoint.sh#L22

In Multus v4.0.2, the thin plugin has the flag defined, but it is not
read and so does not have effect.

This pull request fixes the problem by making the thin plugin correctly
handles `--multus-master-cni-file-name` flag.

Fixes #1226

Signed-off-by: Hidehito Yabuuchi <hyab@preferred.jp>
2024-04-25 18:31:54 +09:00
dependabot[bot]
5a407e1a21 Bump golang.org/x/net from 0.17.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 12:20:28 +00:00
Doug Smith
9c2771b842 Merge pull request #1250 from SchSeba/support_mtu
Bump cni and network-attachment-definition-client to support MTU
2024-04-17 10:17:13 -04:00
Tomofumi Hayashi
897cb1a759 Merge pull request #1261 from dougbtv/deads_upstreampick_fallback
Enforce termination message policy on all platform pods
2024-04-17 03:37:44 +09:00
David Eads
e214154f5f OCPBUGS-28230: enforce termination message policy on all platform pods 2024-04-16 14:08:55 -04:00
Vasilis Remmas
2c796b5956 Add DRA Integration E2E test
Signed-off-by: Vasilis Remmas <vremmas@nvidia.com>
2024-04-11 19:53:00 +02:00
Moshe Levi
202533cf1d support for Dynamic Resource Allocation doc update
Signed-off-by: Moshe Levi <moshele@nvidia.com>
2024-04-11 19:16:46 +02:00
Moshe Levi
40378cabd3 add support for Dynamic Resource Allocation
Signed-off-by: Moshe Levi <moshele@nvidia.com>
2024-04-11 19:16:46 +02:00
Sebastian Sch
30d6aa06e5 Bump cni and network-attachment-definition-client
This is to support the new MTU field in cni network results

Signed-off-by: Sebastian Sch <sebassch@gmail.com>
2024-04-11 17:28:29 +03:00
Tomofumi Hayashi
b6206a0dbf Merge pull request #1242 from k8snetworkplumbingwg/dependabot/go_modules/google.golang.org/protobuf-1.33.0
Bump google.golang.org/protobuf from 1.31.0 to 1.33.0
2024-04-02 15:01:08 +09:00
adrianc
d625d48231 Change Validation of interface name
interface name should not be limited to DNS-1123 label format.
instead validate interface name if provided in pod network annotation
in a similar manner as iproute2[1].

this will allow to request interface names such as: "uplink_p0"

[1]11740815bf/lib/utils.c (L832)

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-03-14 18:05:57 +02:00
Tomofumi Hayashi
0fd3fa7919 Fix typo 2024-03-14 23:16:06 +09:00
Doug Smith
ad81dbf50f Merge pull request #1236 from igsilya/failure-logs
server: More concise error messages.
2024-03-14 10:03:51 -04:00
dependabot[bot]
7ad0dd287a Bump google.golang.org/protobuf from 1.31.0 to 1.33.0
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 23:19:51 +00:00
Tomofumi Hayashi
b09350cf1a Merge pull request #1086 from mengzhuo/master
Delete .travis.yaml
2024-03-01 00:12:11 +09:00
Ilya Maximets
ddc78f1244 server: More concise error messages.
On the CNI request failure, multus-cni prints out cmdArgs.  In all
cases, except for debug printing, this is done with %s and a special
printing function.  However, the handleCNIRequest is an exception for
some reason.  That leads to unintelligible error messages in case
of CNI request failures (severely abridged):

 CmdAdd (shim): CNI request failed with status 400:
 '&{ContainerID:<id> Netns:/var/run/netns/<uuid> IfName:eth0
    Args:<args> Path: StdinData:[125 121 111 117 114 32 97 100 118
    101 114 116 105 115 101 109 101 110 116 32 99 111 117 108 100
    32 98 101 32 104 101 114 101 125 ... another 650 numbers ]}
 ContainerID:"<id>" Netns:"/var/run/netns/<uuid>" IfName:"eth0"
 Args:"<args>" Path:"" ERRORED: error configuring pod ...

printCmdArgs() should be used for this case as well to avoid huge
hardly readable logs.

At the same time, the content of cniCmdArgs is always appended to
the error twice as seen in the example above.  The first time by the
HandleCNIRequest and another time by the handleCNIRequest.  Same for
the HandleDelegateRequest path.

Just removing the prefixing from the lower level handlers while
keeping higher level ones.  The 'ERRORED' part migrated to the higher
level handler functions to preserve the overall look of the error.

Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2024-02-29 00:38:07 +01:00
Tomofumi Hayashi
5f0b4cdc6b Merge pull request #1235 from dougbtv/remove-readiness-check-on-del
Skips checking for readiness on CNI DEL
2024-02-22 23:55:21 +09:00
dougbtv
a1915e1a8e Skips checking for readiness on CNI DEL (and instead warns)
Because deletes should favor a successful path, the readiness check should be skipped for pod removals.

This can cause an issue where there's pods pending deletes and that might impact scheduling of a pod that may be necessary in order to set the readiness indicator.

Adds a new method  to check for readiness indicator alone in order to immediately log a warning.
2024-02-22 09:15:11 -05:00
Patryk Matuszak
53a68c35ff Recreate configs only if base files changed (#1234) 2024-02-21 02:24:15 +09:00
Doug Smith
ca5a4c9aa9 Merge pull request #1230 from s1061123/update-kind
Update kind e2e
2024-02-15 13:25:27 -05:00
Tomofumi Hayashi
03fcb34abe Update kind e2e 2024-02-16 02:30:17 +09:00
Doug Smith
ba18cf5ab3 Merge pull request #1214 from s1061123/add-netdef-informer
Add net-attach-def informer for thick plugin
2024-02-15 09:40:57 -05:00
Doug Smith
b271fbf84d Merge pull request #1229 from s1061123/fix/filepath
Add filepath sanity check
2024-02-14 10:48:12 -05:00
Tomofumi Hayashi
748930239d Add filepath sanity check 2024-02-15 00:29:07 +09:00
Doug Smith
c550826675 Merge pull request #1228 from s1061123/fix/reload-kubeconfig-if-failed
Reload bootstrap kubeconfig if cert mgr failed to load valid certs
2024-02-13 10:49:49 -05:00
Tomofumi Hayashi
a337317533 Reload bootstrap kubeconfig if cert mgr failed to load valid certs
When user recreate whole cluster certs, multus thick plugin's
previous cert is no longer valid. In such case, we need to prevent
to use cert manager's old certs and restart it from bootstrap
kubeconfig. This fix reloads client config from bootstrap
kubeconfig if cert mgr's cert is failed to load pod.
2024-02-14 00:46:12 +09:00
Tomofumi Hayashi
8e5060b9a7 Opt out to mount service account token (#1219) 2024-02-01 17:33:59 +09:00
Dennis Periquet
6c982f3fee supplement log with stringified version of StdinData to enhance debug (#1215) 2024-01-26 01:30:58 +09:00
Doug Smith
1071115e90 Merge pull request #1217 from s1061123/add-sleep-thin
Add additional sleep in thick entrypoint
2024-01-25 11:10:57 -05:00
Tomofumi Hayashi
493d421cf7 Update github actions (#1216) 2024-01-26 00:46:09 +09:00
Tomofumi Hayashi
24b2d55c84 Add additional sleep in thick entrypoint 2024-01-26 00:45:47 +09:00
Tomofumi Hayashi
6812ce0ed6 Update e2e related tools (#1212) 2024-01-24 22:39:54 +09:00
Tomofumi Hayashi
6ac6fe675f Add net-attach-def informer for thick plugin
This change introduces net-attach-def informer in multus-daemon,
thick pluign case. It could reduced API calls to get
net-attach-def.
2024-01-20 02:04:21 +09:00
Fish-pro
3477c9c827 fix(quick start)-You do not need to clone the repository and directly deliver the installation file (#1210)
Signed-off-by: Zechun Chen <zechun.chen@daocloud.io>
2024-01-18 23:45:57 +09:00
Lionel Jouin
36ba3039ae Add watch permission to thick e2e template (#1208)
As described in #1171, the watch function is required in the clusterrole
for the thick Multus version, otherwise "Failed to watch *v1.Pod" would
be returned.
2024-01-18 23:45:40 +09:00
Tomofumi Hayashi
40687759fb Reduce informer memory usage by informer transform (#1203)
This fix reduces multus-daemon memory usage with k8s 0.29 informer
transform to trim unnecessary Pod object information to multus.
2024-01-18 23:32:21 +09:00
Tomofumi Hayashi
a70da3556a Fix a wait to account for the possiblity of a not ready unix socket (#1207) 2024-01-11 13:34:37 +09:00
Doug Smith
003fbd5785 Merge pull request #1202 from s1061123/add-timeout
Add timeout
2024-01-05 08:04:02 -05:00
Tomofumi Hayashi
6e4f62f2f2 disable revive's dot-imports in unit test files 2024-01-05 14:32:09 +09:00
Tomofumi Hayashi
197877d113 Adds a wait to account for the possiblity of a not ready unix socket 2024-01-05 14:27:31 +09:00
Doug Smith
ab7d64e96f Refactors the configuration options document reference (#1180) 2024-01-04 23:54:56 +09:00
dependabot[bot]
acfbd42719 Bump google.golang.org/grpc from 1.53.0 to 1.56.3 (#1182)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.56.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.53.0...v1.56.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 14:48:54 +09:00
Doug Smith
c76db9c7a0 Merge pull request #1194 from s1061123/fix-logging
Fix to use lumberjack only for logging files
2023-12-07 09:14:30 -05:00
Tomofumi Hayashi
540a887651 Fix to use lumberjack only for logging files 2023-12-07 21:08:17 +09:00
Tomofumi Hayashi
d97514f841 Ignore dot-imports error message only for go test files 2023-12-07 20:56:36 +09:00
Moshe Levi
e4404b2645 fix e2e test ModuleNotFoundError: No module named 'pkg_resources' (#1189)
Signed-off-by: Moshe Levi <moshele@nvidia.com>
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2023-12-07 20:51:02 +09:00
Jonatan
a373a2286d Deployments: Add watch permission to thick example (#1171)
The ClusterRole was missing the watch permission on pods, which resulted in Multus throwing this error message every few seconds:

Failed to watch *v1.Pod: unknown (get pods)
2023-12-04 20:28:18 +09:00
dependabot[bot]
e2e8cfb677 Bump golang.org/x/net from 0.8.0 to 0.17.0 (#1176)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.8.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:21:43 +09:00
Doug Smith
b710020f7b Merge pull request #1173 from s1061123/remove-status-set-del
Suppress status unset in cmdDel
2023-10-04 11:50:16 -04:00
Tomofumi Hayashi
46fe38e2c5 Suppress status unset in cmdDel
This change stops to update status in CNI's DEL command.
There are two reasons:

1. cmd DEL is invoked at only pod deletion, hence k8s does not
guarantee the pod and it may be already deleted. Hence this
API may failed.

2. In stateful set's pod recreation case, it may have race
condition to update the status at cmd DEL case.
In stateful set case, same pod name, i.e. stateful-0, is deleted
and then created again. In this case, if old Pod's CNI DEL command
is not finished before new Pod's creation, then SetStatus function
is failed due to pod UID mismatch.
2023-10-04 23:28:26 +09:00
Doug Smith
d7e391e006 Merge pull request #1167 from s1061123/fix-params
Per node certificates: Add duration parameter
2023-09-26 14:14:03 -04:00
Tomofumi Hayashi
6a0c905347 Fix per node cert feature
This change introduces certDuration as parameter to customize
cert duration. In addition, environment variable for node name
is matched to other usages.
2023-09-27 00:54:32 +09:00
Peng Liu
4d69fed8ad Fix incorrect mount volume name in the thick plugin manifest (#1166)
Signed-off-by: Peng Liu <pliu@redhat.com>
2023-09-25 22:31:02 +09:00
Peng Liu
1dd4edded2 Move chroot from multus main process to its child processes (#1161)
We used to run chroot in multus main process when calling other CNI
plugin binary. We also use a mutex to lock the access to pod files.
But this causes performance issues when facing heavy
CNI_ADD/CNI_DEL requests.

With this patch, we do chroot in the child processes instead. So
file operations in the main process will not be affected by chroot.

This change requires the multus thick plugin pod to mount CNI bin
directory to the same path in the container host.

Signed-off-by: Peng Liu <pliu@redhat.com>
2023-09-22 17:08:57 +09:00
Doug Smith
857d070679 Merge pull request #1159 from s1061123/per-node-cert
Add per-node-certification support
2023-09-18 12:16:03 -04:00
Tomofumi Hayashi
e5d19fff6b Add per-node-certification support
This change introduces per-node certification for multus pods.
Once multus pod is launched, then specified bootstrap kubeconfig
is used for initial access, then multus sends CSR request to
kube API to get original certs for kube API access. Once it is
accepted then the multus pod uses generated certs for kube access.
2023-09-19 00:38:29 +09:00
Doug Smith
acfdc64991 Merge pull request #1158 from s1061123/bump-ver
Bump golang and k8s API version
2023-09-17 13:18:16 -04:00
Tomofumi Hayashi
f8afd78120 Bump golang and k8s API version 2023-09-18 01:40:44 +09:00
Doug Smith
ddb977f4b9 Merge pull request #1154 from dcbw/shared-informer
Performance and efficiency improvements in daemon/server mode
2023-09-15 09:56:52 -04:00
Dan Williams
d9c06e99d1 server: don't set CNI config readinessindicatorfile when using ConfigManager
For whatever reason calling os.Stat() on the readiness indicator file
from CmdAdd()/CmdDel() when multus is running in server mode and is
containerized often returns "file not found", which triggers the
polling behavior of GetReadinessIndicatorFile(). This greatly delays
CNI operations that should be pretty quick. Even if an exponential
backoff is used, os.Stat() can still return "file not found"
multiple times, even though the file clearly exists.

But it turns out we don't need to check the readiness file in server
mode when running with MultusConfigFile == "auto". In this mode the
server starts the ConfigManager which (a) waits until the file exists
and (b) fsnotify watches the readiness and (c) exits the daemon
immediately if the file is deleted or moved.

This means we can assume that while the daemon is running and the
server is handling CNI requests that the readiness file exists;
otherwise the daemon would have exited. Thus CmdAdd/CmdDel don't
need to run a lot of possibly failing os.Stat() calls in the CNI
hot paths.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:58:19 -05:00
Dan Williams
b0df7dd5e3 server/config: use filepath.Join()
Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:58:19 -05:00
Dan Williams
fb4f4aa4c1 server/config: un-export some functions no longer used outside the module
Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:58:19 -05:00
Dan Williams
c2add82b93 server/config: fix MonitorPluginConfiguration test
The test was comparing the same configuration to itself, since
nothing in the changed CNI configuration is used in the written
multus configuration.

Instead make sure the updated CNI config contains something
that will be reflected in the written multus configuration,
and while we're there use a more robust way to wait for the
config to be written via gomega.Eventually().

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:58:19 -05:00
Dan Williams
8539a476fd server/config: consolidate ConfigManager start and fsnotify watching
Simplify setup by moving the post-creation operations like
GenerateConfig() and PersistMultusConfig() into a new Start() function
that also begins watching the configuration directory. This better
encapsulates the manager functionality in the object.

We can also get rid of the done channel passed to the config
manager and just use the existing WaitGroup to determine when to
exit the daemon main().

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:58:19 -05:00
Dan Williams
4ade85669b server/config: simplify ConfigManager creation
A couple of the setup variables for NewManager*() are already in the
multus config that it gets passed, so use those instead of passing
explicitly.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:58:19 -05:00
Dan Williams
50c0357467 server: use a shared informer pod cache rather than direct apiserver access
When running in server mode we can use a shared informer to listen for
Pod events from the apiserver, and grab pod info from that cache rather
than doing direct apiserver requests each time.

This reduces apiserver load and retry latency, since multus can poll
the local cache more frequently than it should do direct apiserver
requests.

Oddly static pods don't show up in the informer by the timeout and
require a direct apiserver request. Since static pods are not common
and are typically long-running, it should not be a big issue to
fall back to direct apiserver access for them.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-14 08:57:12 -05:00
Dan Williams
cec1a53cd8 server: simplify server start
Move server start code to a common function that both regular
and test code can use. Also shut down the server from the
testcases.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 07:54:41 -05:00
Dan Williams
1605ffcad5 daemon: remove unused done channel
Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 07:54:41 -05:00
Dan Williams
7c68481e43 vendor: add client-go and more apimachinery modules
We'll need these for the next commit.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 07:54:41 -05:00
Dan Williams
6b8d24c1ef server: make CmdAdd/Del/Check struct member functions
Then we can just use the Server struct kube client and exec rather
than passing them through the function parameters.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 07:54:41 -05:00
Dan Williams
752f28c2bc k8sclient: bump QPS to 50
Multus is a pretty critical piece of infrastructure, so it shouldn't
be subject to the same lower QPS limits as most components are.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 07:54:41 -05:00
Dan Williams
fff8519517 k8sclient: make InClusterK8sClient() call GetK8sClient()
We want the in-cluster client that the multus server uses to use
the same client config (QPS, protobuf, grpc, etc) as the regular
client.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 07:54:41 -05:00
Dan Williams
02ce071abb github: export and upload e2e test kind logs (#1157)
When things go wrong the logs can help figure out why.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2023-09-13 21:51:58 +09:00
Doug Smith
7ea924b8f1 Merge pull request #1148 from s1061123/fix/1126
Add 'cniConfigDir' to specify config dir
2023-08-31 14:16:37 -04:00
Tomofumi Hayashi
f6afc79b47 Add 'cniConfigDir' to specify config dir
Fix #1126
2023-09-01 03:02:29 +09:00
Doug Smith
3a95111901 Merge pull request #1145 from s1061123/refine-build
Refine build script
2023-08-25 11:06:37 -04:00
Tomofumi Hayashi
4456e91b5c Refine build script 2023-08-24 01:52:15 +09:00
Doug Smith
c5a0002be4 Merge pull request #1142 from s1061123/fix/1130
fix multus config file generation to avoid self-delegation
2023-08-16 10:58:56 -04:00
Tomofumi Hayashi
159f2610c0 fix multus config file generation to avoid self-delegation
Check masterConfigPath file and skip if it is multus generated
files. Fix #1130
2023-08-16 21:00:26 +09:00
Doug Smith
8d8aa80cd5 Merge pull request #1101 from AlinaSecret/fix-race-condition
Fix race conditions in logging package functions and enable race detection in tests
2023-08-11 10:29:11 -04:00
Alina Sudakov
272b3ca8fa Fix race conditions in logging package functions
The logging package contains two functions, SetLogOptions and SetLogFile, that could experience race conditions when multiple goroutines access and modify the logger struct concurrently. 
To address these issues, a copy of the logger struct is now created in each function to eliminate data races.

In addition, the test-go.sh script is updated to include the '-race' flag, enabling race detection during testing. This change helps prevent future race conditions by activating the Go race detector.

Signed-off-by: Alina Sudakov <asudakov@redhat.com>
2023-08-10 13:17:19 +03:00
Tomofumi Hayashi
d5883bdbfa Fix multus-daemon quit process (#1133) 2023-08-04 01:16:37 +09:00
Doug Smith
46d446f0e5 Merge pull request #1131 from s1061123/thick-readiness
Support readinessIndicator file in thick multus-daemon
2023-08-01 10:11:18 -04:00
Tomofumi Hayashi
41d5d08686 Support readinessIndicator file in thick multus-daemon
This change supports readinessIndicatorfile in multus-daemon and
refines goroutine termination in case of signal with context.
2023-08-01 23:01:17 +09:00
Doug Smith
bf79dc3269 Merge pull request #1127 from s1061123/add-ready-check
This change introduces wait to generate config until API is ready
2023-07-24 09:54:21 -04:00
Tomofumi Hayashi
82324a7795 This change introduces wait to generate config until API is ready 2023-07-24 22:38:08 +09:00
dependabot[bot]
91a82a1264 Bump google.golang.org/grpc from 1.40.0 to 1.53.0 (#1120)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.40.0 to 1.53.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.40.0...v1.53.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 23:37:26 +09:00
Doug Smith
0bc8103654 Merge pull request #1125 from s1061123/refine-options
Refine and fix parameters
2023-07-20 09:39:05 -04:00
Tomofumi Hayashi
fa60329105 Refine and fix parameters
This changes refines parameters in multus thick/thin.
- delete unused parameter, confDir
- add multus-cni-conf-dir
- fix multusConfigPath in non-default params case
2023-07-20 21:22:54 +09:00
Doug Smith
99c4481e08 Merge pull request #1085 from s1061123/fix/fix-sliceindex
Fix potential issue, slice bounds out of range error
2023-06-29 10:07:42 -04:00
Doug Smith
f03765681f Merge pull request #1095 from s1061123/fix/plugin-without-interface
Fix multus to support CNI plugin which does not create interface
2023-05-18 13:51:56 -04:00
Tomofumi Hayashi
22304806c8 Fix multus to support CNI plugin which does not create interface
This change fixes multus to support CNI plugin which does not
create any interface and return empty result. Some CNI plugin
may do network configuration change only and does not create
any interface and return empty CNI result. Current multus assumes
that CNI config always creates some interfaces hence above CNI
plugin is out of assumption and multus may not work with such
plugins.
2023-05-18 02:40:27 +09:00
Meng Zhuo
649a4c5dd3 Delete .travis.yaml
#559 had already drop this file
2023-05-06 09:32:14 +08:00
Doug Smith
0c37bb043c Merge pull request #1084 from s1061123/fix/del-noerr
Suppress error message in cmdDel, in thick plugin
2023-05-02 14:54:41 -04:00
Tomofumi Hayashi
da704f8f63 Fix potential issue, slice bounds out of range error 2023-05-03 03:42:33 +09:00
Tomofumi Hayashi
5d64ec3367 Suppress error message in cmdDel, in thick plugin
This fix suppress error message in thick plugin's shim, for
DEL command, to align with CNI spec.

Fix #1080
2023-05-03 03:33:01 +09:00
Tomofumi Hayashi
b05ff2db4e Fix origin github action workflows 2023-05-03 03:32:32 +09:00
Doug Smith
b9acfeb6b7 Adds permanent slack invite (#1083) 2023-04-27 23:07:48 +09:00
Tomofumi Hayashi
77aac95dfc Fix github action 2023-04-15 02:07:39 +09:00
Tomofumi Hayashi
72945e3679 Add missing mutex lock for race condition fix (#1074) 2023-04-15 02:02:44 +09:00
Tomofumi Hayashi
1b01e3e486 Change gopkg.in to v4 for v4 release 2023-04-13 23:36:40 +09:00
Tomofumi Hayashi
fe14c17fe7 Refine dockerfile to use buildx multi-arch image for thin plugin (#1070) 2023-04-13 23:15:00 +09:00
Tomofumi Hayashi
66b0c5c371 Update goreleaser (#1069)
This change update goleleaser action and goreleaser config file
to release multus binaries, including multus-daemon and multus-shim
2023-04-13 22:43:30 +09:00
Tomofumi Hayashi
f3a371358a Update golang to 1.19 (#1067) 2023-04-13 22:42:32 +09:00
Tomofumi Hayashi
b4bea43f7e Cleanup code comment (#1068)
This change removes unnecessary comment.
2023-04-13 22:41:14 +09:00
Tomofumi Hayashi
7c22973f9f Add mutex lock for load confs in GetDefaultNetworks (#1073)
Thick server's chroot mutex is missing in GetDefaultNetworks,
that touch the pod filesystem. This change adds mutex lock there
and prevent race condition.

Fix #1072
2023-04-13 22:40:16 +09:00
Doug Smith
487c6fcec4 Merge pull request #1066 from s1061123/fix/thick-log-options
Refactoring thick daemon config processing
2023-04-10 09:40:00 -04:00
Tomofumi Hayashi
c279938e21 Refactoring thick daemon config processing
to damonset config file, hence command line option parsing is no
longer used. This change removes these parts.

Fix #1058.
2023-04-08 01:34:05 +09:00
Tomofumi Hayashi
855e8bee45 Add workaround patch for okd-builder.Dockerfile build failure (#1063)
Fix #1062
2023-04-07 00:30:11 +09:00
Tomofumi Hayashi
5bce250398 Fix linter warning message (#1057) 2023-04-07 00:20:04 +09:00
Miguel Duarte Barroso
1add898a3c Cni daemon config does not need shim socket path (#1059)
* config, daemon: shim socket path is not needed

The shim socket dir attribute is only required for the shim (cni
configuration). Thus, it can be removed from the daemon configuration.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* config, daemon: rename socket dir attribute

Now the socketDir parameter no longer stutters.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

---------

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2023-04-07 00:12:15 +09:00
Miguel Duarte Barroso
7ced7dd10c docs, thick plugin: align docs with new configuration reference (#1055)
* config, daemon: shim socket path is not needed

The shim socket dir attribute is only required for the shim (cni
configuration). Thus, it can be removed from the daemon configuration.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* config, daemon: rename socket dir attribute

Now the socketDir parameter no longer stutters.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* docs, thick plugin: align docs with new configuration reference

PR #1053 - [0] - changed the thick plugin configuration to happen
exclusively via the user provided config map. This PR aligns the multus
documentation with the existing code.

[0] - https://github.com/k8snetworkplumbingwg/multus-cni/pull/1053

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

---------

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2023-04-07 00:11:17 +09:00
Tomofumi Hayashi
21f84c0162 Remove 'cni-version' option in default deployment (#1061)
This change removes 'cni-version' option in entrypoint
from default deployment manifest. This change uses cniVersion of
delegate CNI, cluster network.
2023-04-05 21:24:03 +09:00
Doug Smith
7cf1c2f0c2 Merge pull request #1054 from s1061123/dev/distroless-thin
Change thin container base image to distroless
2023-04-03 14:41:22 -04:00
Tomofumi Hayashi
dcf92c8e94 Change thin container base image to distroless
This commit changes thin container base image to distroless
to simplify container image. It replace old shell script entrypoint
to golang implementation and implement multus installer (i.e. copy).
2023-04-03 15:44:52 +09:00
Miguel Duarte Barroso
6c2e09529b Improve v4 config (#1053)
* Draft for refine options

* config: remove command line args; use configMap/JSON config

The `socketDir` configuration was split in two, since the multus daemon,
and multus shim have the socket in different paths. This allows the user
to customize these paths.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* deployment, ci: update daemonset spec

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

---------

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
Co-authored-by: Tomofumi Hayashi <tohayash@redhat.com>
Co-authored-by: dougbtv <dosmith@redhat.com>
2023-03-30 22:58:03 +09:00
Doug Smith
c129e72779 Merge pull request #1044 from s1061123/dev/remove-old-conf
Remove old/obsolated config from repo
2023-03-02 09:41:44 -05:00
Tomofumi Hayashi
adf5e98b1b Remove old/obsolated config from repo
As of #1012 discussion, several conf file seems to be obsolated
and not updated to track the changes. This change removes these
obsolated files.
2023-03-02 22:33:51 +09:00
杨刚 (成都)
346a54ccd9 Cleanup : github.com/pkg/errors has been archived. (#1035)
Signed-off-by: yanggang <gang.yang@daocloud.io>
2023-02-21 00:39:06 +09:00
dependabot[bot]
6d97cf9071 Bump golang.org/x/net from 0.1.0 to 0.7.0 (#1039)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.1.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 00:38:39 +09:00
杨刚 (成都)
3680725cb0 align case line . (#1033)
Signed-off-by: yanggang <gang.yang@daocloud.io>
2023-02-21 00:38:22 +09:00
Doug Smith
e6299693aa Merge pull request #1028 from s1061123/dev/remove-restart-crio
Remove deprecated 'restart-crio' option
2023-02-20 09:32:30 -05:00
杨刚 (成都)
43e2008107 code clean for if condtion (#1037)
Signed-off-by: yanggang <gang.yang@daocloud.io>
2023-02-14 01:41:51 +09:00
杨刚 (成都)
f5b64339bf clean wrong notes. (#1036)
Signed-off-by: yanggang <gang.yang@daocloud.io>
2023-02-14 01:41:19 +09:00
Tomofumi Hayashi
d1d0da1457 Remove deprecated 'restart-crio' option
'restart-crio' option was used for workaround crio issues.
This issue no longer exists, hence make it obsolate and removed.
2023-02-06 14:37:01 +09:00
Doug Smith
e692127d19 Readme updates, includes thick-plugin-by-default installation text (#1004) 2022-12-23 00:00:08 +09:00
Yury K
0ae7db9bbc Update network-attachment-definition-client to v1.4.0 (#984)
Update network-attachment-definition-client package
to support DeviceInfo spec 1.1.0

Signed-off-by: Yury Kulazhenkov <ykulazhenkov@nvidia.com>

Signed-off-by: Yury Kulazhenkov <ykulazhenkov@nvidia.com>
2022-12-22 23:55:40 +09:00
Doug Smith
95b45eff5d Updates config options and tests for socketfile and cniconfdir between thin/thick (#1002) 2022-12-20 02:37:34 +09:00
Doug Smith
b660d69e18 Merge pull request #998 from s1061123/dev/action-update
Update github action version
2022-12-15 11:17:45 -05:00
Doug Smith
873857273f Merge pull request #999 from s1061123/fix/warning
Fix warning message about package comment
2022-12-15 11:17:16 -05:00
Tomofumi Hayashi
442da10303 Update github action version 2022-12-16 01:10:41 +09:00
Tomofumi Hayashi
3ffa206abc Fix warning message about package comment 2022-12-16 00:43:01 +09:00
Doug Smith
971a110db7 Create an openshift origin (OKD) golang builder image in build pipeline (#992) 2022-12-14 22:50:20 +09:00
Doug Smith
d7a8f962da Merge pull request #969 from liornoy/ginkgov2
Bump ginkgo to v2
2022-12-13 13:26:03 -05:00
Doug Smith
36952b8401 Merge pull request #990 from s1061123/fix/remove-origin
Remove origin from CI build
2022-12-13 13:25:09 -05:00
Tomofumi Hayashi
0a0e9b4db9 Remove origin from CI build
This change remove origin build from CI pipeline because
origin's golang version is too old.
2022-12-14 03:02:55 +09:00
liornoy
7a549fd9ac Update tests source code to ginkgo v2
This commit updates the tests to import ginkgo v2.
2022-12-13 19:37:13 +02:00
liornoy
048438e0ef Bump ginkgo to v2
This commit bumps ginkgo to version v2.5.1.
2022-12-13 19:37:13 +02:00
Tomofumi Hayashi
344fb0ae09 Do 'go mod tidy' to cleanup go.mod/go.sum (#988) 2022-12-14 02:20:42 +09:00
Doug Smith
26902b29b6 Merge pull request #987 from s1061123/dev/go118
Bump go version to 1.18/1.19
2022-12-13 09:48:52 -05:00
Doug Smith
d84add88b9 Merge pull request #986 from s1061123/fix/so-long-gorilla
This removes gorilla/mux from multus
2022-12-13 09:44:04 -05:00
Tomofumi Hayashi
546e715c9e Bump go version to 1.18/1.19
Bump go version to 1.18/1.19, supported in golang community.
2022-12-13 23:30:30 +09:00
Tomofumi Hayashi
1454d912f4 This removes gorilla/mux from multus
This change removes gorilla/mux dependency from the multus-thick
because goriila team no longer support gorilla packages, including
gorilla/mux.
2022-12-13 17:35:34 +09:00
yulng
6eda6d9deb add check test result for interface net1 (#960)
Signed-off-by: yulng <wei.yang@daocloud.io>

Signed-off-by: yulng <wei.yang@daocloud.io>
2022-12-13 17:01:14 +09:00
Ayush Patel
33ce772b98 Add NOTICE (#967)
Signed-off-by: Ayush Patel <patel.ayush08@gmail.com>

Signed-off-by: Ayush Patel <patel.ayush08@gmail.com>
2022-12-08 23:50:37 +09:00
lgtm-com[bot]
b8304cf7dc Add CodeQL workflow for GitHub code scanning (#981)
Co-authored-by: LGTM Migrator <lgtm-migrator@users.noreply.github.com>
2022-12-08 23:47:00 +09:00
Doug Smith
7ce5e9fbdc Merge pull request #976 from s1061123/dev/simple-kubeletpkg
Simplify kubeletclient code to reduce parse url
2022-11-29 11:29:16 -05:00
Miguel Duarte Barroso
a9ace511d8 server: add healthz endpoint (#963)
From the node (or any privileged pod having mounted the multus socket)
you can now query the multus-cni server liveliness - for instance:

```
root@kind-worker:/# curl -v --unix-socket /run/multus/multus.sock localhost/healthz
*   Trying /run/multus/multus.sock:0...
* Connected to localhost (/host/run/multus/multus.sock) port 80 (#0)
> GET /healthz HTTP/1.1
> Host: localhost
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 14 Nov 2022 17:21:07 GMT
< Content-Length: 0
< Connection: close
<
* Closing connection 0
```

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2022-11-29 23:34:13 +09:00
Tomofumi Hayashi
6c52c476bf Simplify kubeletclient code to reduce parse url
This removes unused variables and simplify kubeletclient code.
2022-11-28 18:05:24 +09:00
Doug Smith
c75d773248 Merge pull request #957 from s1061123/fix/948
Change conditional flow to use cache file in CmdDel
2022-11-10 11:12:15 -05:00
Tomofumi Hayashi
82c09a98b7 Change conditional flow to use cache file in CmdDel
This fix changes conditional flow to use net-attach-def if
cache file is collapsed in CmdDel.

Fix #948
2022-11-11 00:22:15 +09:00
yanggang
103e70778b clean code (#952)
Signed-off-by: yanggang <gang.yang@daocloud.io>

Signed-off-by: yanggang <gang.yang@daocloud.io>
2022-11-10 01:00:27 +09:00
yanggang
40d600b336 Upgrade fsnotify (#954)
* This makes checking events a lot easier

* upgrade fsnotify package

Signed-off-by: yanggang <gang.yang@daocloud.io>

Signed-off-by: yanggang <gang.yang@daocloud.io>
2022-11-10 00:35:44 +09:00
yanggang
b6c50f2d07 we should keep the first character Big for aligned info note (#953)
Signed-off-by: yanggang <gang.yang@daocloud.io>

Signed-off-by: yanggang <gang.yang@daocloud.io>
2022-11-10 00:34:28 +09:00
yanggang
4f91106f29 remove io/ioutil for advanced golang (#951)
Signed-off-by: yanggang <gang.yang@daocloud.io>

Signed-off-by: yanggang <gang.yang@daocloud.io>
2022-11-10 00:18:54 +09:00
Tomofumi Hayashi
77e0150afe Fix license boilerplate/copyright in go files (#947)
This change fix license boilerplate and its copyright.
The updated year in copyright is based on the file creation date.
If older than 2021, added copyright is transfered to multus
authors from Intel corporation as the multus code was officially
transfered to Kubernetes Networking Plumbing Working Group on
March 11, 2021.
2022-11-02 21:49:57 +09:00
Moshe Levi
6f8fa8c286 fix resource api grpc failed to connect to unix socket (#946)
After commit c6fa047212
resource api got broken in 2 places. first place handled by f530d3eb84
The second break was in replacing podresources.GetV1Client with getKubeletResourceClient.
The GetV1Client remove the protocol (e.g unix:) by calling GetAddressAndDialer, but the getKubeletResourceClient
is using the full endpoint (with the portocol) causing failed to connect to unix socket.
This patch remove the unix: prefix before the grpc connect.

Fixes: #944
Signed-off-by: Moshe Levi <moshele@nvidia.com>

Signed-off-by: Moshe Levi <moshele@nvidia.com>
2022-11-02 21:47:55 +09:00
yulng
8550fa62a5 Modify the judgment of test results (#936) 2022-10-31 22:34:10 +09:00
Gao PeiLiang
a3f9694d09 delegate plugin delete success, delete cache file (#926) 2022-10-31 22:32:51 +09:00
Moshe Levi
fd893ab625 grpc: move to use grpc.WithTransportCredentials() (#942)
see 2c8bfad910

Signed-off-by: Moshe Levi <moshele@nvidia.com>

Signed-off-by: Moshe Levi <moshele@nvidia.com>
2022-10-29 00:47:05 +09:00
Moshe Levi
f530d3eb84 fix kubeletSocket scope (#941)
The kubeletSocket in the function is scoping the const kubeletSocket
which causing resource api to fail with

"stat /var/lib/kubelet/pod-resources/.sock: no such file or directory"

Signed-off-by: Moshe Levi <moshele@nvidia.com>

Signed-off-by: Moshe Levi <moshele@nvidia.com>
2022-10-26 22:25:38 +09:00
Doug Smith
e43ed9274e Merge pull request #939 from s1061123/fix/server
Fix multus-daemon in case of user given config
2022-10-24 16:03:48 -04:00
yulng
f52aa691d6 Add "EOF" to the Cat command, otherwise the operation will fail (#932) (#934)
Add EOF to the Cat command, otherwise the operation will fail
2022-10-25 00:57:09 +09:00
Tomofumi Hayashi
46daed0654 Fix multus-daemon in case of user given config
This fix the issue #931 to refining server startup code including
file copy and channel processing, with simplification.
2022-10-25 00:46:40 +09:00
yulng
e9bb5e5f48 Add "EOF" to the Cat command, otherwise the operation will fail (#932)
Add EOF to the Cat command, otherwise the operation will fail
2022-10-23 14:58:08 +09:00
Doug Smith
fa8a6e6880 Merge pull request #925 from s1061123/dev/rc-api
Support IPs, MAC and cni-args in delegate API
2022-10-14 11:34:12 -04:00
Tomofumi Hayashi
6f90a0f075 Support IPs, MAC and cni-args in delegate API 2022-10-15 00:12:17 +09:00
Doug Smith
efa8c62ec4 Merge pull request #930 from s1061123/fix/mod-ver
Fix kubelet version in go.mod
2022-10-14 11:07:58 -04:00
Tomofumi Hayashi
7c619d64a6 Fix kubelet version in go.mod
fix #928
2022-10-14 23:32:26 +09:00
Eoghan Russell
1aac2431b8 Fix broken link in README (#919) 2022-09-26 23:26:25 +09:00
Doug Smith
f372831536 Merge pull request #915 from s1061123/fix/delegate-api
Fix deleate API to load conflist
2022-09-20 13:19:58 -04:00
Tomofumi Hayashi
3e97437e31 Fix deleate API to load conflist 2022-09-21 01:56:53 +09:00
Miguel Duarte Barroso
b1aba95892 Idiomatic config unit tests (#908)
* gomega, unit tests: use `HaveLen` matcher

The `HaveLen` matcher provides a better error msg when the test fails,
resulting in easier debugging.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* gomega, unit tests: use `BeEmpty` matcher

The `BeEmpty` matcher is more idiomatic, and results in better error
msgs when the test fails.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* gomega, unit tests: use `BeTrue` and `BeFalse` matchers

These matchers are more idiomatic.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2022-09-19 10:22:08 -04:00
Doug Smith
371123659b Merge pull request #906 from TimFroidcoeur/fix_multiple_default_gw
fix multiple default gw
2022-09-01 16:18:38 -04:00
Tim Froidcoeur
8da20f38ea fix multiple default gw
when the configuration specifies both an IPv4 and IPv6 default route,
the IsFilterV4Gateway and IsFilterV6Gateway flags should both be false,
to allow the gateway configuration.
The logic in CheckGatewayConfig would do the inverse, setting both to
true in case of both IPv4 and IPv6 gateway.

Fixes: d52f2b6a ("Update libcni cache when default-route net selection
is used")
Signed-off-by: Tim Froidcoeur <tim.froidcoeur@tessares.net>
2022-09-01 16:50:19 +02:00
Doug Smith
d93a3bb736 Merge pull request #899 from jklare/jk_readme_thick_plugin
rephrase readme section describing thin vs thick plugin
2022-08-26 15:11:52 -04:00
Doug Smith
e882d51b3c Merge pull request #903 from s1061123/dev/fedora-image
[WIP]Change container image to python:slim from centos
2022-08-26 14:35:23 -04:00
Tomofumi Hayashi
db7cc6bc80 Change container image to fedora from centos
This change introduce fedora container for multus image.
2022-08-27 02:57:58 +09:00
Doug Smith
33c0d1bd86 Merge pull request #900 from s1061123/fix/image-tag
Fix container tags for thick plugin
2022-08-25 11:29:17 -04:00
Tomofumi Hayashi
12c6c91586 Fix container tags for thick plugin 2022-08-25 23:07:32 +09:00
Jan Klare
2f12b5ce99 rephrase readme section describing thin vs thick plugin
Signed-off-by: Jan Klare <jan.klare@bisdn.de>
2022-08-24 14:45:26 +02:00
Doug Smith
0c515497d7 recommends thick plugin in quickstart (#894) 2022-08-24 20:23:33 +09:00
Doug Smith
68a9593f84 Merge pull request #897 from s1061123/fix/add-disclaimers
Update deployment yaml
2022-08-22 13:35:21 -04:00
Tomofumi Hayashi
95c4d0c1c5 Update deployment yaml
This change adds note section in deployment yaml to explicitly
mention the scope of this yaml.
2022-08-23 01:19:24 +09:00
Doug Smith
5498080119 Merge pull request #893 from k8snetworkplumbingwg/feature/multus-4.0
Feature/multus 4.0
2022-08-18 16:48:02 -04:00
Tomofumi Hayashi
3d9cec4ec9 Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-08-19 00:07:30 +09:00
Tomofumi Hayashi
505ab4567c Add delegate API in multus-daemon (#890)
This changes introduce delegate API function in multus-daemon.
This API will be consumed from other programs for hot-plug
interface into running pod. This change also cleanups server
code to split into client code and server code to easy to import
from other golang code.
2022-08-10 00:45:23 +09:00
Doug Smith
8dee74d7b9 Merge pull request #870 from s1061123/dev/doc-modified
Add 4.0 document
2022-08-01 11:19:02 -04:00
Doug Smith
fba6d66720 Merge pull request #886 from s1061123/update-libcni
Bump libcni to fetch the bugfix
2022-07-29 11:06:14 -04:00
Tomofumi Hayashi
7f7bb354c5 Bump libcni to fetch the bugfix
This change introduces https://github.com/containernetworking/cni/pull/904
to fix the issue.
2022-07-29 20:31:57 +09:00
Doug Smith
f4c0adf54c Merge pull request #881 from chrisplo/detect-python3
Detect python3
2022-07-22 09:48:40 -04:00
Chris Plock
ac6757b9cc Detect python3
fixes https://github.com/k8snetworkplumbingwg/multus-cni/issues/880
2022-07-15 15:13:27 -07:00
Doug Smith
70c970cd6e Merge pull request #878 from tomkukral/print-kubeconfig
write output file for generated kubeconfig
2022-07-12 08:39:17 -04:00
Doug Smith
3deb079546 Merge pull request #876 from s1061123/dev/update-golang
Update golang version to 1.18.x
2022-07-11 16:11:44 -04:00
Doug Smith
9cb6fb57bf Merge pull request #877 from s1061123/dev/refine-build
Refine build-go.sh and update 'version' output
2022-07-11 16:10:31 -04:00
Tomáš Kukrál
95adff55b0 write output file for generated kubeconfig
This change will make debugging easier in cases when non-default
location is used and user forgot to mount new directory.
2022-07-08 14:02:51 +02:00
Tomofumi Hayashi
99dd6678d5 Refine build-go.sh and update 'version' output 2022-07-07 01:44:13 +09:00
Tomofumi Hayashi
6f5fdf64c7 Update golang version to 1.18.x 2022-07-07 00:31:25 +09:00
Tomofumi Hayashi
9d67fbd520 Add 4.0 document 2022-06-27 22:12:23 +09:00
Doug Smith
ca8c9c5791 Merge pull request #864 from s1061123/default-route-pointer
Use *[]net.IP for 'default-route' network selection element.
2022-06-21 15:47:09 -04:00
Doug Smith
18a660ebc7 Merge pull request #867 from mmirecki/downwardapi
Fix missing device-info in networks-status annotation for chained plugins
2022-06-21 14:38:29 -04:00
mmirecki
358f09bfe2 Merge remote-tracking branch 'upstream/master' into downwardapi 2022-06-21 20:14:34 +02:00
Tomofumi Hayashi
107624ccff Use *[]net.IP for 'default-route' network selection element. 2022-06-22 02:12:08 +09:00
Doug Smith
13eb83a01c Merge pull request #868 from s1061123/fix/revive-err
Fix revive error
2022-06-21 11:26:44 -04:00
Tomofumi Hayashi
dc57189cf9 Fix revive error 2022-06-21 23:39:18 +09:00
Doug Smith
3cda380ad1 Merge pull request #865 from s1061123/fix/m40-revive-err
Fix revive's error
2022-06-21 10:24:15 -04:00
mmirecki
2bf8dae9a8 Fix missing device-info in networks-status annotation for chained plugins 2022-06-21 14:47:04 +02:00
Tomofumi Hayashi
2993671acd Fix revive's error 2022-06-21 00:58:24 +09:00
Tomofumi Hayashi
a735987501 Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-06-14 18:04:33 +09:00
Doug Smith
8bbb594dad Merge pull request #862 from s1061123/fix/cmddel-nostatus-update
Skip status update in CmdDel if getPod is failed
2022-06-13 13:22:17 -04:00
Tomofumi Hayashi
fcc8e44f14 Skip status update in CmdDel if getPod is failed
This change skips to update pod's network-status annotation
when getPod is failed at the beginning of CmdDel. If getPod is
failed, K8s api gets stucked in many cases, hence pod update
might be failed in most cases.
2022-06-14 02:14:43 +09:00
Doug Smith
3572647e5b Merge pull request #860 from s1061123/dev/add-ut
Add unit-test to increase test coverage
2022-06-10 10:39:20 -04:00
Doug Smith
2eb5d2f653 Merge pull request #857 from s1061123/dev/metrics
Add metrics support
2022-06-10 10:38:34 -04:00
疯狂的小企鹅
be56f8dc30 Fixed that in.Delegates may remain in the CmdDel (#859)
Co-authored-by: jinda.ljd <jinda.ljd@alibaba-inc.com>
2022-06-08 21:03:14 +09:00
Tomofumi Hayashi
580b72a5b2 Add unit-test to increase test coverage 2022-06-08 13:49:17 +09:00
Tomofumi Hayashi
a77d3cbedb Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-06-06 23:37:59 +09:00
Miguel Duarte Barroso
d2a4b832f2 config, logging: correct the logging parameters (#856)
The logging parameters were listing using uppercase, which is wrong. 

According to multus configuration, they should be in camelCase - [0] and [1].

[0] - 779170a48e/pkg/types/types.go (L45)
[1] - 779170a48e/pkg/types/types.go (L46)
2022-06-06 22:33:05 +09:00
Tomofumi Hayashi
9f3e6b0da0 Add metrics support
This change introduces prometheus metric exporter support for
multus-daemon, thick plugin.
2022-06-04 00:45:37 +09:00
Tomofumi Hayashi
df903a757e Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-06-02 18:04:49 +09:00
Nikhil Simha
779170a48e Added hyperlink for logging (#851)
* Added hyperlink for logging

This may make it easier for users to find info on logging parameters since there isn't a dedicated docs page for it.

* Fixed hyperlink

Linked to proper section
2022-05-26 09:33:22 -04:00
Doug Smith
5ee0274b5b Bumps net-attach-def client library to v1.1.1 (#846)
This accounts for CNI v1.0 formatted IP address information (no longer requires version field)
2022-05-12 22:58:23 +09:00
Doug Smith
42a2642852 Merge pull request #844 from danielmellado/fix-images-docs
Fix wrong location for daemonset in readme
2022-05-10 11:42:14 -04:00
Daniel Mellado
b5323e4144 Fix wrong location for daemonset in readme
This PR fixes an outdated reference in the docs where
multus-daemonset.yml was expected to be found under the images folder.
2022-05-10 17:06:47 +02:00
Tomofumi Hayashi
485642c18f Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-05-07 00:36:30 +09:00
Doug Smith
dcbc215b93 The cachefile name should be the delegate configuration name (#841)
It was previously using the net-attach-def name, which doesn't align with the cache file. Causing default-route selection to not succeed.
2022-05-07 00:06:37 +09:00
Doug Smith
5ede36fb43 Merge pull request #839 from s1061123/dev/refine-k8s-lib
Refine Kubernetes APIs
2022-05-04 13:27:00 -04:00
Tomofumi Hayashi
16bd359bc0 Update k8s api to v1.22.8 2022-04-28 22:55:55 +09:00
Tomofumi Hayashi
c6fa047212 Remove k8s.io/kubernetes dependency from multus
Fix #675
2022-04-26 18:22:36 +09:00
Tomofumi Hayashi
59415ad0d6 Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-04-26 16:46:16 +09:00
Dan Williams
ecc1482d50 types: fix usage of strings.Split() for parsing CNI_ARGS (#836)
* types: fix usage of strings.Split() for parsing CNI_ARGS

strings.Split() returns a slice, in this case with two elements of
the key and value. As such we shouldn't range over the slice when
the code is expecting a 2-element slice of key/value.

Otherwise we get errors for valid CNI_ARGS like:

2022-04-22T11:53:54Z [error] CreateCNIRuntimeConf: CNI_ARGS K8S_POD_NAMESPACE=openshift-etcd K8S_POD_NAMESPACE 17 is not recognized as CNI arg, skipped
2022-04-22T11:53:54Z [error] CreateCNIRuntimeConf: CNI_ARGS K8S_POD_NAMESPACE=openshift-etcd openshift-etcd 14 is not recognized as CNI arg, skipped

Fixes: d7d2a99ab5 ("Replace setenv with runtimeConfig set")

Signed-off-by: Dan Williams <dcbw@redhat.com>

* types/conf: fix handling of CNI_ARGS additions to rt.Args

We want to set the CNI_ARGS value in rt.Args if the existing value
is empty, not if the key doesn't exist yet. Since the rt.Args array
is pre-created with the K8S args keys, empty values of those keys
couldn't be overwritten with the previous scheme that just checked
if the key existed.

If the CNI_ARGS key wasn't found in rt.Args then add it; previously
a typo ("isExists != false") prevented that with inverted logic.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2022-04-26 00:13:09 +09:00
Doug Smith
b34486308c Merge pull request #834 from s1061123/fix/sriov
Fix sr-iov support
2022-04-25 09:51:23 -04:00
Nikhil Simha
dd15abc9b1 Added a static pod e2e test. (#835)
* Added bash e2e test for static pods. Checks for net1 interface on pod.

Signed-off-by: nicklesimba <simha.nikhil@gmail.com>

* Enable static pod test in CI

Signed-off-by: nicklesimba <simha.nikhil@gmail.com>

* Addressed review comments

Signed-off-by: nicklesimba <simha.nikhil@gmail.com>
2022-04-22 00:35:39 +09:00
Tomofumi Hayashi
10fcc49f4d Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-04-19 00:18:38 +09:00
jinglina
706de7c2c6 delete type conversion (#833) 2022-04-18 22:34:40 +09:00
Doug Smith
ea0df58e7c Merge pull request #831 from s1061123/update-golang
Update golang version to 1.17
2022-04-18 09:33:43 -04:00
Tomofumi Hayashi
4670f1f240 Fix sr-iov support
Fix thick plugin daemonset to add volume mapping required for
sr-iov and fix code to update network status.
In addition, fix checkpoint structures to support K8s without
kubelet pod resources API.

fix #665 and #778
2022-04-18 21:28:13 +09:00
Tomofumi Hayashi
4ada0c3ae8 Update golang version to 1.17 2022-04-14 23:30:30 +09:00
Doug Smith
13e4b3a1c4 Merge pull request #828 from s1061123/dev/chroot
Add chroot option in multus-daemon
2022-04-13 15:38:56 -04:00
Tomofumi Hayashi
282b40a503 Add chroot option in multus-daemon
This change introduces 'chroot' option in multus-daemon to execute
delegate CNI with host filesystem context.
2022-04-13 19:36:53 +09:00
Tomofumi Hayashi
bf4d6c716c Merge remote-tracking branch 'origin/master' into feature/multus-4.0 2022-04-12 21:42:19 +09:00
Tomofumi Hayashi
77ed17b392 Fix letter 2022-04-12 15:06:44 +09:00
Cyclinder
6bcb6bf403 add some flags for customize the log settings (#817) 2022-04-12 00:22:48 +09:00
Doug Smith
588ee8f192 Fixes log message on CNI Check (#825) 2022-04-11 20:35:47 +09:00
Cyclinder
ce533f01cc update e2e tools version for kind and cni-plugins (#830) 2022-04-11 14:08:22 +09:00
Tomofumi Hayashi
579d83f359 Merge remote-tracking branch 'origin' into feature/multus-4.0 2022-04-06 00:42:23 +09:00
Tomofumi Hayashi
51c39205a8 Remove error handling for getPod to force to proceed cmdDel.
In cmdDel, CNI Spec mentioned that plugin should proceed cmdDel
without any error, hence the change removes error returning
at cmdDel.

fix #822
2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
4180f88442 Refine multus-daemon config 2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
d1046fa1c9 Fix install binary for thick plugin 2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
0a144e597a Fix thick plugin to run kind-e2e test 2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
2f5af62a92 Refine unit test in pkg/multus
Some of tests are duplicated hence deduped. In addition, sort
these unit tests based on cniVersion.
2022-04-06 00:34:53 +09:00
Cyclinder
80693bde62 fix the usage of flag "overrideNetworkName" (#805) 2022-04-06 00:34:53 +09:00
Doug Smith
3abb21a80c crio: mount /run rslave (#802)
to prevent "unknown FS magic on "/var/run/netns/*": 1021994" errors

Signed-off-by: Peter Hunt <pehunt@redhat.com>

Co-authored-by: Peter Hunt <pehunt@redhat.com>
2022-04-06 00:34:53 +09:00
Doug Smith
e7aaf8f5d5 only warn when netns can't be opened (#803) 2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
93ec0c121e Support CNI 1.0.0
Fix #792
2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
ecc98be9c6 Split multus unit tests into several files
To simplify multus unit tests, split it into several files,
based on testing CNI version.
2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
b8d4d46462 check version incompatibility (#762) (#798)
* multus: entrypoint: disallow incompatible cni versions

When top level CNI version is 0.4.0 or more, nested CNI version
can't be less than 0.4.0 since these are incompatible. This
closes issue #737.

Signed-off-by: Balazs Nemeth <bnemeth@redhat.com>

* multus: thick: disallow incompatible cni versions

Similarly to disallowing incompatible versions in entrypoint.sh,
add the same logic in go for the thick plugin.

Signed-off-by: Balazs Nemeth <bnemeth@redhat.com>

* multus: add unit test for incompatible cni versions

Signed-off-by: Balazs Nemeth <bnemeth@redhat.com>

Co-authored-by: Balazs Nemeth <bnemeth@redhat.com>
2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
869b94ffaa Simplify e2e scripts (#795) 2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
d4a30c383d Make binary file and directory name consistent
This change make binary file and directory name consistent.
In addition, change the package name cni to server because cni
is a bit umbiguous for cni plugin's repository.
2022-04-06 00:34:53 +09:00
Tomofumi Hayashi
d4a3ea4fd0 Replace setenv with runtimeConfig set (#785)
setenv refers environment variables, which is unique in process,
not unique to go routine. Hence it may causes some issue in multi
threaded case, hence it is replaced with libcni's runtimeConfig
value set to set these variables at libcni side, after process
fork.
2022-04-06 00:34:53 +09:00
Miguel Duarte Barroso
fb31217e2c thick-plugin: refactor multus
Multus is refactored as a thick plugin, featuring 2 main components:
  - a server listening to a unix domain socket, running in a pod
  - a shim, a binary on the host that will send JSON requests built from
    its environment / stdin values to the aforementioned server.

The pod where the multus daemon is running must share the host's PID
namespace.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

react to maintainers review

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

thick, deployment: update the daemonset spec

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

thick, config: validate the cni config passed by the runtime

Without this patch, we're blindly trusting anything sent by the server.
This way, we assure the requests arriving at the multus controller are
valid before hand.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

thick: model client / server config

Also add a new command line parameter on the multus controller, pointing
it to the server configuration.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

SQUASH candidate, thick, config: cleanup the configuration

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

multus: use args.args instead of an env variable

CNI is already filling the args structure; we should consume that
rather than rely on the environment variables.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

unit tests: remove weird tests that check an impossible scenario

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

docs, thick: document the thick plugin variant

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

thick, server, multus: re-use common types

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2022-04-06 00:34:52 +09:00
Doug Smith
0453b52097 Merge pull request #823 from s1061123/fix/cmddel
Remove error handling for getPod to force to proceed cmdDel.
2022-04-04 15:41:36 -04:00
Tomofumi Hayashi
2d53334211 Remove error handling for getPod to force to proceed cmdDel.
In cmdDel, CNI Spec mentioned that plugin should proceed cmdDel
without any error, hence the change removes error returning
at cmdDel.

fix #822
2022-04-05 02:29:41 +09:00
Cyclinder
45428a53ce fix the usage of flag "overrideNetworkName" (#805) 2022-03-04 16:01:21 +09:00
Doug Smith
6c12dc8c4f crio: mount /run rslave (#802)
to prevent "unknown FS magic on "/var/run/netns/*": 1021994" errors

Signed-off-by: Peter Hunt <pehunt@redhat.com>

Co-authored-by: Peter Hunt <pehunt@redhat.com>
2022-03-04 02:02:37 +09:00
Doug Smith
7559625a38 only warn when netns can't be opened (#803) 2022-03-04 02:02:24 +09:00
Balazs Nemeth
450e1d3414 check version incompatibility (#762)
* multus: entrypoint: disallow incompatible cni versions

When top level CNI version is 0.4.0 or more, nested CNI version
can't be less than 0.4.0 since these are incompatible. This
closes issue #737.

Signed-off-by: Balazs Nemeth <bnemeth@redhat.com>

* multus: thick: disallow incompatible cni versions

Similarly to disallowing incompatible versions in entrypoint.sh,
add the same logic in go for the thick plugin.

Signed-off-by: Balazs Nemeth <bnemeth@redhat.com>

* multus: add unit test for incompatible cni versions

Signed-off-by: Balazs Nemeth <bnemeth@redhat.com>
2022-02-28 21:50:39 +09:00
Tomofumi Hayashi
6dd45f38f9 Replace setenv with runtimeConfig set (#785)
setenv refers environment variables, which is unique in process,
not unique to go routine. Hence it may causes some issue in multi
threaded case, hence it is replaced with libcni's runtimeConfig
value set to set these variables at libcni side, after process
fork.
2022-02-21 23:55:33 +09:00
Doug Smith
843147aca0 Merge pull request #775 from s1061123/fix/773
Suppress uid mismatch error/warning in case of static pod
2022-02-07 12:44:04 -05:00
Miguel Duarte Barroso
191b8cb0ec e2e tests: always pull new multus images (#783)
Currently, the local workflow is far from optimal, since for every
change on the multus images the developers are required to redeploy
the kind cluster.

A more efficient workflow would be to build a local image, upload it to
the kind cluster, and finally re-deploy (delete & re-provision) the
daemonset, which would be running the new version.

For this flow to be possible, the multus container `imagePullPolicy`
must be set to `Always` - [0] - otherwise, the image is not updated.

[0] - https://kubernetes.io/docs/concepts/containers/images/#updating-images

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2022-01-26 21:37:03 +09:00
Miguel Duarte Barroso
9e79b79a89 multus, e2e tests: allow event sending to k8s API (#776)
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2022-01-20 23:50:16 +09:00
Tomofumi Hayashi
2e474f4c95 Suppress uid mismatch error/warning in case of static pod
In static pod case, kube api returns mirror pod UID hence
uid must be mismatched. This fix suppress error/warning message
in such case.

Fix #773
2022-01-15 23:17:53 +09:00
Doug Smith
27a86dafbc Merge pull request #779 from nicklesimba/patch-3
Update development.md
2022-01-14 11:10:03 -05:00
Nikhil Simha
daf96bffb3 Update development.md
Fixed doc for how to test
2022-01-14 10:59:29 -05:00
Doug Smith
7a53c910f2 Merge pull request #774 from nicklesimba/patch-2
Update quickstart.md
2022-01-11 13:59:45 -05:00
Nikhil Simha
38b1cd1cec Update quickstart.md
Specified location of 00-multus.conf for clarity
2022-01-11 13:57:48 -05:00
Sho Shimizu
6adb4dc4c4 Add toleration against NoExecute (#768) 2022-01-07 00:06:19 +09:00
Miguel Duarte Barroso
12df5bda72 run gofmt on the code (#772)
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2022-01-05 01:33:58 +09:00
Doug Smith
ed18a1f175 Merge pull request #764 from s1061123/fix/default-route
Update libcni cache when default-route net selection is used
2021-12-15 12:28:40 -05:00
Tomofumi Hayashi
d52f2b6a45 Update libcni cache when default-route net selection is used
To keep consistency between actual network and CNI result in cache,
update libcni cache when multus add/del default routes by
`default-route` network selection.
2021-12-15 01:57:51 +09:00
Doug Smith
4d9731bd3a Merge pull request #757 from maiqueb/fix-flaky-config-regen-test
flaky: fix delegate CNI conf updates unit test
2021-12-14 10:06:01 -05:00
Miguel Duarte Barroso
0da5449854 thick, config regen, test: fix test
The test was just checking that a READ/WRITE fsnotify.Event for
the multus configuration was being seen; this patch changes this
behavior, and assures that the delegateCNI configuration update results
in turn on the update of the multus configuration file.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-12-14 10:10:15 +01:00
Miguel Duarte Barroso
653c4b481d thick, config regen, tests: use a unique path for multus config
Using a unique path for the config generation will lessen the
amount of events caught by the test that checks if the multus
configuration must be re-generated as a result a default cluster
configuration update.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-12-09 14:46:36 +01:00
Doug Smith
b9d0d93d6e Pod UID mismatches should only warn on CNI DEL (#763) 2021-11-23 17:52:45 +09:00
Balazs Nemeth
84fde9d711 Update images/README.md (#761) 2021-11-19 21:22:34 +09:00
Doug Smith
4e0e65044b Merge pull request #758 from maiqueb/fix-legacy-entrypoint-e2e-tests
CI, e2e tests: fix legacy
2021-11-15 08:18:59 -05:00
Miguel Duarte Barroso
70660236a8 CI, e2e tests: fix legacy
Our CI is currently mistakenly executing the thick img on the e2e
legacy lanes. Furthermore, the e2e daemonset spec provided features
(and uses) the kubeconfig / multus conf generation binaries
provided only on the thick image.

This commit addresses these by enabling the e2e `setup_cluster.sh`
script user to specify the path to the desired deployment
configuration.

Github workflows are updated accordingly.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-11-15 12:38:26 +01:00
Moritz Kröger
130db696ca Update quickstart.md (#754)
* Update quickstart.md

removed unnecassary dollarsign since it kills the quick copy functionality of Code in Github

* Update README.md

* Update quickstart.md

* Update how-to-use.md

Co-authored-by: Doug Smith <douglaskippsmith@gmail.com>
2021-11-12 00:24:06 +09:00
Doug Smith
ada145ca5f Merge pull request #753 from s1061123/fix/cni-bin-dir
Added --cni-bin-dir and --multus-bin-file for regression
2021-11-11 00:00:46 +09:00
Tomofumi Hayashi
25c46c84b8 Added --cni-bin-dir and --multus-bin-file for regression 2021-11-08 17:40:54 +09:00
Etienne Champetier
32c952e501 Fixup thick deployment, make images thiner (#747)
* deployment, thick: only use thick image

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>

* images: only keep binaries and LICENSE

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2021-11-02 14:20:37 +09:00
Tomofumi Hayashi
7091831a00 Remove dependency of go-dproxy (#746)
This change removes to dependency of go-dproxy from multus to
reducing library dependencies.
2021-10-28 12:15:11 -04:00
Tomofumi Hayashi
1e43784d4c Change the sort of image push. 2021-10-28 23:04:07 +09:00
Miguel Duarte Barroso
42fde2292d deployment, thick: remove extra multus-cni-config mountpoint (#745)
This volume was removed since multus now requires the default cluster
network CNI configuration to be available. As such, the volume as
removed, but we unfortunately forgot to remove to remove the volume
mount.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-10-28 09:37:35 -04:00
Miguel Duarte Barroso
8ba2accb9f Replace entrypoint script with initcontainers (#718)
* build: install the multus binary in an init container

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: generate kubeconfig via go

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: generate multus cni configuration via golang

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: provide a docker img for daemon based deployments

We will have 2 different images (only on amd64 archs):
- legacy entrypoint script based
- daemonized process

The `image-build` docker action is updated, to build these 2 images.

There will be 2 different deployment specs, along with e2e test
lanes, one for each of the aforementioned alternatives.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: delegate CNI config watch loop via golang

For the thick-plugin alternative, provide the watch loop for
configuration regeneration via a golang binary.

Over time, this binary is expected to run the control loop to watch
out for pod updates.

To enable current multus users to chose when they upgrade to this new
deployment setup, these changes are provided in separate multus images,
having a different yaml spec files. Both of these alternatives are
tested e2e, since a new lane is introduced.

The following libraries are introduced, along with the motivation for
adding them:
- dproxy: allows traversing the default network configuration arbitrarily,
  similar to what an X path / JSON path tool provides.
  Repo is available at [0].
- fsnotify: watch for changes in the default CNI configuration file.
  Repo is available at [1].

The config map providing the default network CNI configuration is not
copied over, since originally, the user was not required to install a
default network CNI plugin first, but, nowadays, this is a required
step of multus.

As such, it is no longer required to provide a default CNI
configuration.

[0] - https://github.com/koron/go-dproxy
[1] - https://github.com/fsnotify/fsnotify

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* run gofmt

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* refactor: make the builder pattern more idiomatic to golang

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: update github actions to release new imgs

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-10-27 08:42:37 -04:00
Dan Williams
b56dd5f67f multus: test Pod UID scenarios when both passed and not passed by runtime
Because the tests fake out the OS exec functionality, the environment that
would usually be passed to a plugin instead gets passed as a string array
to the ExecPlugin() function in the tests. But when verifying the expected
environment this function was looking at the actual OS environment rather
than the passed string array. Fix that.

Use that to test various cases of the pod UID being passed to plugins.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-10-26 01:20:43 +09:00
Dan Williams
e8e99f1771 multus: log error marshaling delegate netconf
Fixes an unused variable warning too.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-10-26 01:20:43 +09:00
Dan Williams
cb19a22cb9 multus: fail if given pod UID does not match Kube API pod UID
If the runtime passes a pod UID via K8S_POD_UID (which both CRIO and
containerd do as of mid-2021) then fail if the pod we get from the
Kube API has a different UID. This would indicate that the pod was
deleted and recreated while Multus was attempting to set up
networking for the old pod instance's sandbox, and it's pointless
to continue setting up a sandbox for a dead pod instance.

Also pass the pod UID through to plugins so they can perform
additional checking and validation on the pods they get from the
Kube API.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2021-10-26 01:20:43 +09:00
Miguel Duarte Barroso
392726842f e2e tests: allow podman OCI
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-10-20 20:58:32 +09:00
Tomofumi Hayashi
76c31b0861 Fix version string in '-v' option
Due to change the file layout, previous version command does not
work. This change fix it.
2021-10-15 00:07:20 +09:00
Doug Smith
51a74efe57 Merge pull request #729 from s1061123/dev/update-layout
Change file layout and mention the supported Kubernetes version
2021-09-29 10:19:02 -04:00
Tomofumi Hayashi
bf0b37e010 Change file layout and mention the supported Kubernetes version 2021-09-28 22:40:51 +09:00
3968 changed files with 533826 additions and 118156 deletions

View File

@@ -4,18 +4,18 @@ jobs:
build:
strategy:
matrix:
go-version: [1.16.x, 1.17.x]
go-version: [1.22.x, 1.23.x]
goarch: [386, amd64, arm, arm64, ppc64le, s390x]
os: [ubuntu-latest] #, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Build
env:

41
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "46 8 * * 0"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ go ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"

View File

@@ -1,110 +1,82 @@
name: Image build
on: [pull_request]
jobs:
build-amd64:
name: Image build/amd64
build-thin:
name: Image build thin plugin
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
# note: disable sbom/provenance for now (gchr.io does not managed well yet)
- name: Build container image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest-amd64
file: deployments/Dockerfile
tags: ghcr.io/${{ github.repository }}:latest
file: images/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x
sbom: false
provenance: false
build-arm64:
name: Image build/arm64
# note: disable sbom/provenance for now (gchr.io does not managed well yet)
- name: Build container debug image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest
file: images/Dockerfile.debug
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
build-thick:
name: Image thick plugin
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Build container image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest-arm64
file: deployments/Dockerfile.arm64
build-arm32:
name: Image build/arm32
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build container image
uses: docker/build-push-action@v2
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest-arm32
file: deployments/Dockerfile.arm32
build-ppc64le:
name: Image build/ppc64le
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build container image
uses: docker/build-push-action@v2
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest-ppc64le
file: deployments/Dockerfile.ppc64le
build-s390:
name: Image build/s390x
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build container image
uses: docker/build-push-action@v2
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest-s390x
file: deployments/Dockerfile.s390x
tags: ghcr.io/${{ github.repository }}:latest-thick
file: images/Dockerfile.thick
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
build-origin:
name: Image build/origin
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Build container image
uses: docker/build-push-action@v2
with:
context: .
push: false
tags: ghcr.io/${{ github.repository }}:latest-origin
file: deployments/Dockerfile.openshift
- name: Download OKD Builder Dockerfile
run: curl https://raw.githubusercontent.com/okd-project/images/main/builder/Dockerfile -o images/okd-builder.Dockerfile
- name: Patch OKD Builder Dockerfile to workaround error
run: sed -i -e "s/yum install -y yum-utils/rpm --import \/etc\/pki\/rpm-gpg\/*;yum install -y yum-utils/" images/okd-builder.Dockerfile
- name: Create root for builder
run: mkdir root
- name: Organically build golang builder image
run: docker build -t local/okdbuilder:latest -f images/okd-builder.Dockerfile .
- name: Organically build Multus origin image
run: docker build -t local/multus-cni:latest-origin -f images/Dockerfile.openshift .

View File

@@ -1,225 +1,116 @@
name: Image push for master
on:
on:
push:
branches:
- master
env:
image-push-owner: 'k8snetworkplumbingwg'
jobs:
push-amd64:
name: Image push/amd64
push-thick:
name: Image push thick image
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
- name: Push container image for thick plugin
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-amd64
ghcr.io/${{ github.repository }}:snapshot-amd64
file: deployments/Dockerfile
ghcr.io/${{ github.repository }}:latest-thick
ghcr.io/${{ github.repository }}:snapshot-thick
file: images/Dockerfile.thick
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
push-arm64:
name: Image push/arm64
push-thin:
name: Image push thin image
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
- name: Push thin container image
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-arm64
ghcr.io/${{ github.repository }}:snapshot-arm64
file: deployments/Dockerfile.arm64
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:snapshot
file: images/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
push-arm32:
name: Image push/arm32
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
- name: Push thin container debug image
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-arm32
ghcr.io/${{ github.repository }}:snapshot-arm32
file: deployments/Dockerfile.arm32
ghcr.io/${{ github.repository }}:latest-debug
ghcr.io/${{ github.repository }}:snapshot-debug
file: images/Dockerfile.debug
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
push-ppc64le:
name: Image push/ppc64le
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
# TODO: need to fix this action
# push-origin:
# name: Image push/origin
# runs-on: ubuntu-latest
# steps:
# - name: Check out code into the Go module directory
# uses: actions/checkout@v4
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
#
# - name: Login to GitHub Container Registry
# if: github.repository_owner == 'k8snetworkplumbingwg'
# uses: docker/login-action@v3
# with:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - name: Push container image
# if: github.repository_owner == 'k8snetworkplumbingwg'
# uses: docker/build-push-action@v5
# with:
# context: .
# push: true
# tags: |
# ghcr.io/${{ github.repository }}:latest-origin
# ghcr.io/${{ github.repository }}:snapshot-origin
# file: images/Dockerfile.openshift
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-ppc64le
ghcr.io/${{ github.repository }}:snapshot-ppc64le
file: deployments/Dockerfile.ppc64le
push-s390x:
name: Image push/s390x
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-s390x
ghcr.io/${{ github.repository }}:snapshot-s390x
file: deployments/Dockerfile.s390x
push-origin:
name: Image push/origin
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-origin
ghcr.io/${{ github.repository }}:snapshot-origin
file: deployments/Dockerfile.openshift
push-manifest:
needs: [push-amd64, push-arm64, push-ppc64le, push-s390x]
runs-on: ubuntu-latest
env:
REPOSITORY: ghcr.io/${{ github.repository }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest for multi-arch images
if: github.repository_owner == 'k8snetworkplumbingwg'
run: |
# get artifacts from previous steps
docker pull ${{ env.REPOSITORY }}:snapshot-amd64
docker pull ${{ env.REPOSITORY }}:snapshot-arm64
docker pull ${{ env.REPOSITORY }}:snapshot-arm32
docker pull ${{ env.REPOSITORY }}:snapshot-ppc64le
docker pull ${{ env.REPOSITORY }}:snapshot-s390x
docker pull ${{ env.REPOSITORY }}:latest-amd64
docker pull ${{ env.REPOSITORY }}:latest-arm64
docker pull ${{ env.REPOSITORY }}:latest-arm32
docker pull ${{ env.REPOSITORY }}:latest-ppc64le
docker pull ${{ env.REPOSITORY }}:latest-s390x
docker manifest create ${{ env.REPOSITORY }}:snapshot ${{ env.REPOSITORY }}:snapshot-amd64 ${{ env.REPOSITORY }}:snapshot-arm64 ${{ env.REPOSITORY }}:snapshot-arm32 ${{ env.REPOSITORY }}:snapshot-ppc64le ${{ env.REPOSITORY }}:snapshot-s390x
docker manifest annotate ${{ env.REPOSITORY }}:snapshot ${{ env.REPOSITORY }}:snapshot-amd64 --arch amd64
docker manifest annotate ${{ env.REPOSITORY }}:snapshot ${{ env.REPOSITORY }}:snapshot-arm64 --arch arm64
docker manifest annotate ${{ env.REPOSITORY }}:snapshot ${{ env.REPOSITORY }}:snapshot-arm32 --arch arm
docker manifest annotate ${{ env.REPOSITORY }}:snapshot ${{ env.REPOSITORY }}:snapshot-ppc64le --arch ppc64le
docker manifest annotate ${{ env.REPOSITORY }}:snapshot ${{ env.REPOSITORY }}:snapshot-s390x --arch s390x
docker manifest push ${{ env.REPOSITORY }}:snapshot
docker manifest create ${{ env.REPOSITORY }}:latest ${{ env.REPOSITORY }}:latest-amd64 ${{ env.REPOSITORY }}:latest-arm64 ${{ env.REPOSITORY }}:latest-arm32 ${{ env.REPOSITORY }}:latest-ppc64le ${{ env.REPOSITORY }}:latest-s390x
docker manifest annotate ${{ env.REPOSITORY }}:latest ${{ env.REPOSITORY }}:latest-amd64 --arch amd64
docker manifest annotate ${{ env.REPOSITORY }}:latest ${{ env.REPOSITORY }}:latest-arm64 --arch arm64
docker manifest annotate ${{ env.REPOSITORY }}:latest ${{ env.REPOSITORY }}:latest-arm32 --arch arm
docker manifest annotate ${{ env.REPOSITORY }}:latest ${{ env.REPOSITORY }}:latest-ppc64le --arch ppc64le
docker manifest annotate ${{ env.REPOSITORY }}:latest ${{ env.REPOSITORY }}:latest-s390x --arch s390x
docker manifest push ${{ env.REPOSITORY }}:latest

View File

@@ -1,22 +1,24 @@
name: Image push release
on:
on:
push:
tags:
- v*
env:
image-push-owner: 'k8snetworkplumbingwg'
jobs:
push-amd64:
name: Image push/amd64
push-thick:
name: Image push thick image
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -24,35 +26,39 @@ jobs:
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
flavor: |
latest=false
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
- name: Push container image for thick plugin
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:stable-amd64
${{ steps.docker_meta.outputs.tags }}-amd64
file: deployments/Dockerfile
ghcr.io/${{ github.repository }}:stable-thick
${{ steps.docker_meta.outputs.tags }}-thick
file: images/Dockerfile.thick
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
push-arm64:
name: Image push/arm64
push-thin:
name: Image push thin image/amd64
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -60,215 +66,73 @@ jobs:
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
flavor: |
latest=false
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
- name: Push thin container image
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:stable-arm64
${{ steps.docker_meta.outputs.tags }}-arm64
file: deployments/Dockerfile.arm64
ghcr.io/${{ github.repository }}:stable
${{ steps.docker_meta.outputs.tags }}
file: images/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
push-arm32:
name: Image push/arm32
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
- name: Push thin container debug image
if: ${{ github.repository_owner == env.image-push-owner }}
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:stable-arm32
${{ steps.docker_meta.outputs.tags }}-arm32
file: deployments/Dockerfile.arm32
ghcr.io/${{ github.repository }}:stable-debug
${{ steps.docker_meta.outputs.tags }}-debug
file: images/Dockerfile.debug
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/ppc64le,linux/s390x
sbom: false
provenance: false
push-ppc64le:
name: Image push/ppc64le
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:stable-ppc64le
${{ steps.docker_meta.outputs.tags }}-ppc64le
file: deployments/Dockerfile.ppc64le
push-s390x:
name: Image push/s390x
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:stable-s390x
${{ steps.docker_meta.outputs.tags }}-s390x
file: deployments/Dockerfile.s390x
push-origin:
name: Image push/origin
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
- name: Push container image
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:stable-origin
${{ steps.docker_meta.outputs.tags }}-origin
file: deployments/Dockerfile.openshift
push-manifest:
needs: [push-amd64, push-arm64, push-ppc64le, push-s390x]
runs-on: ubuntu-latest
env:
REPOSITORY: ghcr.io/${{ github.repository }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: ghcr.io/${{ github.repository }}
tag-latest: false
- name: Login to GitHub Container Registry
if: github.repository_owner == 'k8snetworkplumbingwg'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest for multi-arch images
if: github.repository_owner == 'k8snetworkplumbingwg'
run: |
# get artifacts from previous steps
docker pull ${{ steps.docker_meta.outputs.tags }}-amd64
docker pull ${{ steps.docker_meta.outputs.tags }}-arm64
docker pull ${{ steps.docker_meta.outputs.tags }}-arm32
docker pull ${{ steps.docker_meta.outputs.tags }}-ppc64le
docker pull ${{ steps.docker_meta.outputs.tags }}-s390x
docker manifest create ${{ steps.docker_meta.outputs.tags }} ${{ steps.docker_meta.outputs.tags }}-amd64 ${{ steps.docker_meta.outputs.tags }}-arm64 ${{ steps.docker_meta.outputs.tags }}-arm32 ${{ steps.docker_meta.outputs.tags }}-ppc64le ${{ steps.docker_meta.outputs.tags }}-s390x
docker manifest annotate ${{ steps.docker_meta.outputs.tags }} ${{ steps.docker_meta.outputs.tags }}-amd64 --arch amd64
docker manifest annotate ${{ steps.docker_meta.outputs.tags }} ${{ steps.docker_meta.outputs.tags }}-arm64 --arch arm64
docker manifest annotate ${{ steps.docker_meta.outputs.tags }} ${{ steps.docker_meta.outputs.tags }}-arm32 --arch arm
docker manifest annotate ${{ steps.docker_meta.outputs.tags }} ${{ steps.docker_meta.outputs.tags }}-ppc64le --arch ppc64le
docker manifest annotate ${{ steps.docker_meta.outputs.tags }} ${{ steps.docker_meta.outputs.tags }}-s390x --arch s390x
docker manifest push ${{ steps.docker_meta.outputs.tags }}
docker pull ${{ env.REPOSITORY }}:stable-amd64
docker pull ${{ env.REPOSITORY }}:stable-arm64
docker pull ${{ env.REPOSITORY }}:stable-arm32
docker pull ${{ env.REPOSITORY }}:stable-ppc64le
docker pull ${{ env.REPOSITORY }}:stable-s390x
docker manifest create ${{ env.REPOSITORY }}:stable ${{ env.REPOSITORY }}:stable-amd64 ${{ env.REPOSITORY }}:stable-arm64 ${{ env.REPOSITORY }}:stable-arm32 ${{ env.REPOSITORY }}:stable-ppc64le ${{ env.REPOSITORY }}:stable-s390x
docker manifest annotate ${{ env.REPOSITORY }}:stable ${{ env.REPOSITORY }}:stable-amd64 --arch amd64
docker manifest annotate ${{ env.REPOSITORY }}:stable ${{ env.REPOSITORY }}:stable-arm64 --arch arm64
docker manifest annotate ${{ env.REPOSITORY }}:stable ${{ env.REPOSITORY }}:stable-arm32 --arch arm
docker manifest annotate ${{ env.REPOSITORY }}:stable ${{ env.REPOSITORY }}:stable-ppc64le --arch ppc64le
docker manifest annotate ${{ env.REPOSITORY }}:stable ${{ env.REPOSITORY }}:stable-s390x --arch s390x
docker manifest push ${{ env.REPOSITORY }}:stable
# TODO: need to fix this action
# push-origin:
# name: Image push/origin
# runs-on: ubuntu-latest
# steps:
# - name: Check out code into the Go module directory
# uses: actions/checkout@v4
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
#
# - name: Login to GitHub Container Registry
# if: github.repository_owner == 'k8snetworkplumbingwg'
# uses: docker/login-action@v3
# with:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - name: Docker meta
# id: docker_meta
# uses: crazy-max/ghaction-docker-meta@v1
# with:
# images: ghcr.io/${{ github.repository }}
# tag-latest: false
#
# - name: Push container image
# if: github.repository_owner == 'k8snetworkplumbingwg'
# uses: docker/build-push-action@v5
# with:
# context: .
# push: true
# tags: |
# ghcr.io/${{ github.repository }}:stable-origin
# ${{ steps.docker_meta.outputs.tags }}-origin
# file: images/Dockerfile.openshift

View File

@@ -3,29 +3,71 @@ on: [push, pull_request]
jobs:
e2e-kind:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- docker-file: images/Dockerfile.thick
cni-version: "0.3.1"
multus-manifest: multus-daemonset-thick.yml
- docker-file: images/Dockerfile
cni-version: "0.3.1"
multus-manifest: multus-daemonset.yml
- docker-file: images/Dockerfile.thick
cni-version: "0.4.0"
multus-manifest: multus-daemonset-thick.yml
- docker-file: images/Dockerfile
cni-version: "0.4.0"
multus-manifest: multus-daemonset.yml
# need to wait kind to support CNI 1.0.0 (now kind 0.11 supports up to 0.4.0)
# - docker-file: images/Dockerfile.thick
# cni-version: "1.0.0"
# multus-manifest: multus-thick-daemonset.yml
# - docker-file: images/Dockerfile
# cni-version: "1.0.0"
# multus-manifest: multus-daemonset.yml
env:
JOB_NAME: "${{ matrix.cni-version }}-${{ matrix.multus-manifest }}"
if: >
(( github.event.pull_request.head.repo.owner.login != github.event.pull_request.base.repo.owner.login ) &&
github.event_name == 'pull_request' ) || (github.event_name == 'push' && github.event.commits != '[]' )
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Setup j2cli
run: |
sudo apt-get install -y j2cli
echo $(j2 --version)
- name: Setup registry
run: docker run -d --restart=always -p "5000:5000" --name "kind-registry" registry:2
- name: Build latest-amd64
run: docker build -t localhost:5000/multus:e2e -f deployments/Dockerfile .
- name: Push to local registry
run: docker push localhost:5000/multus:e2e
uses: docker/build-push-action@v5
with:
context: .
load: true
tags: localhost:5000/multus:e2e
file: ${{ matrix.docker-file }}
platforms: linux/amd64
- name: Get kind/kubectl/koko
working-directory: ./e2e
run: ./get_tools.sh
- name: generate yaml files
working-directory: ./e2e
run: env CNI_VERSION=${{ matrix.cni-version }} ./generate_yamls.sh
- name: Setup cluster
working-directory: ./e2e
run: ./setup_cluster.sh
run: MULTUS_MANIFEST=${{ matrix.multus-manifest }} MULTUS_DOCKERFILE=none ./setup_cluster.sh
- name: Test simple pod
working-directory: ./e2e
@@ -35,12 +77,31 @@ jobs:
working-directory: ./e2e
run: ./test-simple-macvlan1.sh
- name: Test static pod
working-directory: ./e2e
run: ./test-static-pod.sh
- name: Test default route1
working-directory: ./e2e
run: ./test-default-route1.sh
# - name: Test DRA integration
# working-directory: ./e2e
# run: ./test-dra-integration.sh
- name: Export kind logs
if: always()
run: |
mkdir -p /tmp/kind/logs
kind export logs --loglevel=debug /tmp/kind/logs
- name: Upload kind logs
if: always()
uses: actions/upload-artifact@v4
with:
name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}
path: /tmp/kind/logs
- name: cleanup cluster and registry
run: |
kind delete cluster
docker kill kind-registry
docker rm kind-registry

View File

@@ -8,17 +8,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.15.x
go-version: 1.22.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: release --rm-dist

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This pull request is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.'

View File

@@ -4,23 +4,22 @@ jobs:
test:
strategy:
matrix:
go-version: [1.16.x, 1.17.x]
go-version: [1.22.x, 1.23.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Run Revive
run: |
GO111MODULE=off go get github.com/mgechev/revive
$(go env GOPATH)/bin/revive -exclude ./vendor/... ./... # this is ouput for user
$(go env GOPATH)/bin/revive -exclude ./vendor/... ./...| xargs -0 -r false # this is for github actions
- name: Run Revive Action by pulling pre-built image
uses: docker://morphy/revive-action:v2
with:
exclude: "./vendor/..."
- name: Run go fmt
run: go fmt ./...

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
# Binary output dir
bin/
e2e/bin/
e2e/yamls/
e2e/repos/
# GOPATH created by the build script
gopath/

View File

@@ -6,18 +6,51 @@ before:
hooks:
- go mod download
builds:
-
env:
- CGO_ENABLED=0
main: ./cmd/
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
- s390x
- env:
- CGO_ENABLED=0
id: multus
binary: multus
main: ./cmd/multus
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
- s390x
ldflags:
- -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.version={{ .Tag }} -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.commit={{ .Commit }} -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.date={{ .Date }}
- env:
- CGO_ENABLED=0
id: multus-daemon
binary: multus-daemon
main: ./cmd/multus-daemon
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
- s390x
ldflags:
- -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.version={{ .Tag }} -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.commit={{ .Commit }} -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.date={{ .Date }}
- env:
- CGO_ENABLED=0
id: multus-shim
binary: multus-shim
main: ./cmd/multus-shim
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
- s390x
ldflags:
- -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.version={{ .Tag }} -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.commit={{ .Commit }} -X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.date={{ .Date }}
archives:
- wrap_in_directory: true
checksum:

View File

@@ -1,116 +0,0 @@
os: linux
language: go
# see https://docs.travis-ci.com/user/reference/overview/#Virtualization-environments
# for the detail
# sudo: requried
dist: bionic
services:
- docker
go:
- 1.13.x
env:
global:
- GO111MODULE=on
- REGISTRY_USER=${REGISTRY_USER:-nfvpe}
- REGISTRY_PASS=${REGISTRY_PASS}
- REPOSITORY_NAME=${REPOSITORY_NAME}
- REPOSITORY_USER=${REPOSITORY_USER}
- DOCKER_CLI_EXPERIMENTAL="enabled"
- secure: "${REGISTRY_SECURE}"
jobs:
- TARGET=amd64
- TARGET=ppc64le
before_install:
- if [ "${REPOSITORY_NAME}" = "" ]; then export REPOSITORY_NAME=multus; fi
- sudo apt-get update -qq
- go get github.com/mattn/goveralls
install:
- go get -u golang.org/x/lint/golint
before_script:
# Make gopath... to run golint/go fmt/go vet
# Suppress golint for fixing lint later.
- golint ./... | grep -v vendor | grep -v ALL_CAPS | xargs -r false
- go fmt ./...
- go vet ./...
# - gocyclo -over 15 ./multus
script:
- GOARCH="${TARGET}" ./hack/build-go.sh
- |
if [ "${TARGET}" == "amd64" ]; then
sudo env PATH=${PATH} ./scripts/test.sh
goveralls -coverprofile=coverage.out -service=travis-ci
docker build -t ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 .
docker build -t ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le -f Dockerfile.ppc64le .
docker build -t ${REPOSITORY_USER}/${REPOSITORY_NAME}-origin:latest -f Dockerfile.openshift .
fi
deploy:
# Release on versioned tag (e.g. v1.0)
- provider: script
#cleanup: false
script: curl -sL https://git.io/goreleaser
on:
tags: true
all_branches: true
condition: "$TARGET = amd64 && $TRAVIS_TAG =~ ^v[0-9].*$ && ! -z $GITHUB_TOKEN && $TRAVIS_OS_NAME = linux"
# Push images to Dockerhub on tag
- provider: script
cleanup: false
script: >
bash -c '
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest;
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable;
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-amd64;
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:$TRAVIS_TAG;
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-ppc64le;
docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASS";
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-amd64;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-ppc64le;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:$TRAVIS_TAG;
export DOCKER_CLI_EXPERIMENTAL="enabled";
docker manifest create ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 --arch amd64;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le --arch ppc64le;
docker manifest push ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest;
docker manifest create ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-ppc64le;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-amd64 --arch amd64;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable-ppc64le --arch ppc64le;
docker manifest push ${REPOSITORY_USER}/${REPOSITORY_NAME}:stable;
echo done'
on:
tags: true
all_branches: true
condition: "$TRAVIS_TAG =~ ^v[0-9].*$ && -n $REGISTRY_USER && -n $REGISTRY_PASS && -n $REPOSITORY_NAME && -n $REPOSITORY_USER"
# Push images to Dockerhub on merge to master
- provider: script
on:
branch: master
condition: "-n $REGISTRY_USER && -n $REGISTRY_PASS && -n $REPOSITORY_NAME && -n $REPOSITORY_USER"
script: >
bash -c '
docker tag ${REPOSITORY_USER}/:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot;
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-amd64;
docker tag ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-ppc64le;
docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASS";
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-amd64;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-ppc64le;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64;
docker push ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le;
docker manifest create ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-ppc64le;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-amd64 --arch amd64;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot-ppc64le --arch ppc64le;
docker manifest push ${REPOSITORY_USER}/${REPOSITORY_NAME}:snapshot;
docker manifest create ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-amd64 --arch amd64;
docker manifest annotate ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest-ppc64le --arch ppc64le;
docker manifest push ${REPOSITORY_USER}/${REPOSITORY_NAME}:latest;
echo done'

2
NOTICE Normal file
View File

@@ -0,0 +1,2 @@
Copyright 2016 Intel Corporation
Copyright 2021 Multus Authors

View File

@@ -22,30 +22,45 @@ Here's an illustration of the network interfaces attached to a pod, as provision
## Quickstart Installation Guide
The quickstart installation method for Multus requires that you have first installed a Kubernetes CNI plugin to serve as your pod-to-pod network, which we refer to as your "default network" (a network interface that every pod will be created with). Each network attachment created by Multus will be in addition to this default network interface. For more detail on installing a default network CNI plugins, refer to our [quick-start guide](docs/quickstart.md).
The quickstart installation method for Multus requires that you have first installed a Kubernetes CNI plugin to serve as your pod-to-pod network, which we refer to as your "default network" (a network interface that every pod will be created with). Each network attachment created by Multus will be in addition to this default network interface. For more detail on installing a default network CNI plugin, refer to our [quick-start guide](docs/quickstart.md).
Clone this GitHub repository, we'll apply a daemonset which installs Multus using to `kubectl` from this repo. From the root directory of the clone, apply the daemonset YAML file:
To use latest features try command below which applies a daemonset and installs thick Multus using `kubectl`:
```
$ cat ./images/multus-daemonset.yml | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml
```
This will configure your systems to be ready to use Multus CNI, but, to get started with adding additional interfaces to your pods, refer to our complete [quick-start guide](docs/quickstart.md)
## Additional installation Options
## Thin Plugin v.s Thick Plugin
With the multus 4.0 release, we introduce a new client/server-style plugin deployment. This new deployment is called ['thick plugin'](docs/thick-plugin.md), in contrast to deployments in previous versions, which is now called a 'thin plugin'. The new thick plugin consists of two binaries, multus-daemon and multus-shim CNI plugin. The 'multus-daemon' will be deployed to all nodes as a local agent and supports additional features, such as metrics, which were not available with the 'thin plugin' deployment before. Due to these additional features, the 'thick plugin' comes with the trade-off of consuming more resources than the 'thin plugin'.
We recommend using the thick plugin in most environments, but if you wish to run the thin plugin, or are in a resource-constrained environment, you may do so with:
```
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml
```
## Additional Installation Options
In addition to the [quick-start guide](docs/quickstart.md), you may:
- Install via daemonset using the quick-start guide, above.
- Download binaries from [release page](https://github.com/k8snetworkplumbingwg/multus-cni/releases)
- By Docker image from [Docker Hub](https://hub.docker.com/r/nfvpe/multus/tags/)
- By Docker image from [GitHub Container Registry](https://github.com/orgs/k8snetworkplumbingwg/packages/container/package/multus-cni)
- Or, roll-your-own and build from source
- See [Development](docs/development.md)
## Comprehensive Documentation
- [How to use](docs/how-to-use.md)
- [Quick Start Guide](docs/quickstart.md)
- [Configuration](docs/configuration.md)
- [Development](docs/development.md)
- [Development and Support Information](docs/development.md)
- [Thick Plugin](docs/thick-plugin.md)
## Contact Us
For any questions about Multus CNI, feel free to ask a question in #general in the [NPWG Slack](https://npwg-team.slack.com/), or open up a GitHub issue. Request an invite to NPWG slack [here](https://intel-corp.herokuapp.com/).
For any questions about Multus CNI, open up a GitHub issue or feel free to ask a question in #general in the [NPWG Slack](https://npwg-team.slack.com/).
To be invited, use [this slack invite link](https://join.slack.com/t/npwg-team/shared_invite/zt-1u2vmsn2b-tKdOokdPY73zn9B32JoAOg).

371
cmd/cert-approver/main.go Normal file
View File

@@ -0,0 +1,371 @@
// Copyright (c) 2023 Network Plumbing Working Group
//
// 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 is Kubernetes controller which approves CSR submitted by multus.
// This command is required only if multus runs with per-node certificate.
package main
// Note: cert-approver should be simple, just approve multus' CSR, hence
// this go code should not have any dependencies from pkg/, if possible,
// to keep its code simplicity.
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"os/signal"
"reflect"
"strings"
"syscall"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/wait"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/certificate/csr"
"k8s.io/client-go/util/workqueue"
)
// CertController object
type CertController struct {
clientset kubernetes.Interface
queue workqueue.RateLimitingInterface
informer cache.SharedIndexInformer
broadcaster record.EventBroadcaster
recorder record.EventRecorder
commonNamePrefixes string
}
const (
maxDuration = time.Hour * 24 * 365
resyncPeriod time.Duration = time.Second * 3600 // resync every one hour, default is 10 hour
maxRetries = 5
)
var (
// ControllerName provides controller name
ControllerName = "csr-approver"
// NamePrefix specifies which name in certification request should be target to approve
NamePrefix = "system:multus"
// Organization specifies which org in certification request should be target to approve
Organization = []string{"system:multus"}
// Groups specifies which group in certification request should be target to approve
Groups = sets.New[string]("system:nodes", "system:multus", "system:authenticated")
// UserPrefixes specifies which name prefix in certification request should be target to approve
UserPrefixes = sets.New[string]("system:node", NamePrefix)
// Usages specifies which usage in certification request should be target to approve
Usages = sets.New[certificatesv1.KeyUsage](
certificatesv1.UsageDigitalSignature,
certificatesv1.UsageClientAuth)
)
// NewCertController creates certcontroller
func NewCertController() (*CertController, error) {
var clientset kubernetes.Interface
// setup Kubernetes API client
config, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
clientset, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
informer := cache.NewSharedIndexInformer(
cache.NewListWatchFromClient(
clientset.CertificatesV1().RESTClient(),
"certificatesigningrequests", corev1.NamespaceAll, fields.Everything()),
&certificatesv1.CertificateSigningRequest{},
resyncPeriod,
nil)
broadcaster := record.NewBroadcaster()
broadcaster.StartLogging(klog.Infof)
broadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: clientset.CoreV1().Events("")})
recorder := broadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "cert-approver"})
queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
c := &CertController{
clientset: clientset,
informer: informer,
queue: queue,
commonNamePrefixes: NamePrefix,
broadcaster: broadcaster,
recorder: recorder,
}
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if csr, ok := obj.(*certificatesv1.CertificateSigningRequest); ok {
if c.filterCSR(csr) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
}
}
}
},
})
return c, nil
}
// Run starts controller
func (c *CertController) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()
klog.Info("Starting cert approver")
go c.informer.Run(stopCh)
if !cache.WaitForCacheSync(stopCh, c.HasSynced) {
utilruntime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
return
}
klog.Info("cert approver synced and ready")
wait.Until(c.runWorker, time.Second, stopCh)
}
// HasSynced is required for the cache.Controller interface.
func (c *CertController) HasSynced() bool {
return c.informer.HasSynced()
}
// LastSyncResourceVersion is required for the cache.Controller interface.
func (c *CertController) LastSyncResourceVersion() string {
return c.informer.LastSyncResourceVersion()
}
func (c *CertController) runWorker() {
for c.processNextItem() {
// continue looping
}
}
func (c *CertController) processNextItem() bool {
// Wait until there is a new item in the working queue
key, quit := c.queue.Get()
if quit {
return false
}
// Tell the queue that we are done with processing this key. This unblocks the key for other workers
// This allows safe parallel processing because two pods with the same key are never processed in
// parallel.
defer c.queue.Done(key)
// Invoke the method containing the business logic
err := c.processItem(key.(string))
// Handle the error if something went wrong during the execution of the business logic
c.handleErr(err, key)
return true
}
// handleErr checks if an error happened and makes sure we will retry later.
func (c *CertController) handleErr(err error, key interface{}) {
if err == nil {
// Forget about the #AddRateLimited history of the key on every successful synchronization.
// This ensures that future processing of updates for this key is not delayed because of
// an outdated error history.
c.queue.Forget(key)
return
}
// This controller retries 5 times if something goes wrong. After that, it stops trying.
if c.queue.NumRequeues(key) < maxRetries {
klog.Infof("Error syncing csr %s: %v", key, err)
// Re-enqueue the key rate limited. Based on the rate limiter on the
// queue and the re-enqueue history, the key will be processed later again.
c.queue.AddRateLimited(key)
return
}
c.queue.Forget(key)
// Report to an external entity that, even after several retries, we could not successfully process this key
utilruntime.HandleError(err)
klog.Infof("Dropping csr %q out of the queue: %v", key, err)
}
func (c *CertController) processItem(key string) error {
startTime := time.Now()
obj, _, err := c.informer.GetIndexer().GetByKey(key)
if err != nil {
return fmt.Errorf("Error fetching object with key %s from store: %v", key, err)
}
req, _ := obj.(*certificatesv1.CertificateSigningRequest)
nodeName := "unknown"
defer func() {
klog.Infof("Finished syncing CSR %s for %s node in %v", req.Name, nodeName, time.Since(startTime))
}()
if len(req.Status.Certificate) > 0 {
klog.V(5).Infof("CSR %s is already signed", req.Name)
return nil
}
if isApprovedOrDenied(&req.Status) {
klog.V(5).Infof("CSR %s is already approved/denied", req.Name)
return nil
}
csrPEM, _ := pem.Decode(req.Spec.Request)
if csrPEM == nil {
return fmt.Errorf("failed to PEM-parse the CSR block in .spec.request: no CSRs were found")
}
x509CSR, err := x509.ParseCertificateRequest(csrPEM.Bytes)
if err != nil {
return fmt.Errorf("failed to parse the CSR bytes: %v", err)
}
i := strings.LastIndex(req.Spec.Username, ":")
if i == -1 || i == len(req.Spec.Username)-1 {
return fmt.Errorf("failed to parse the username: %s", req.Spec.Username)
}
ctx := context.Background()
prefix := req.Spec.Username[:i]
nodeName = req.Spec.Username[i+1:]
if !UserPrefixes.Has(prefix) {
return c.denyCSR(ctx, req, fmt.Sprintf("CSR %q was created by an unexpected user: %q", req.Name, req.Spec.Username))
}
if errs := validation.IsDNS1123Subdomain(nodeName); len(errs) != 0 {
return c.denyCSR(ctx, req, fmt.Sprintf("extracted node name %q is not a valid DNS subdomain %v", nodeName, errs))
}
if usages := sets.New[certificatesv1.KeyUsage](req.Spec.Usages...); !usages.Equal(Usages) {
return c.denyCSR(ctx, req, fmt.Sprintf("CSR %q was created with unexpected usages: %v", req.Name, usages.UnsortedList()))
}
if !Groups.HasAll(req.Spec.Groups...) {
return c.denyCSR(ctx, req, fmt.Sprintf("CSR %q was created by a user with unexpected groups: %v", req.Name, req.Spec.Groups))
}
expectedSubject := fmt.Sprintf("%s:%s", c.commonNamePrefixes, nodeName)
if x509CSR.Subject.CommonName != expectedSubject {
return c.denyCSR(ctx, req, fmt.Sprintf("expected the CSR's commonName to be %q, but it is %q", expectedSubject, x509CSR.Subject.CommonName))
}
if !reflect.DeepEqual(x509CSR.Subject.Organization, Organization) {
return c.denyCSR(ctx, req, fmt.Sprintf("expected the CSR's organization to be %v, but it is %v", Organization, x509CSR.Subject.Organization))
}
if req.Spec.ExpirationSeconds == nil {
return c.denyCSR(ctx, req, fmt.Sprintf("CSR %q was created without specyfying the expirationSeconds", req.Name))
}
if csr.ExpirationSecondsToDuration(*req.Spec.ExpirationSeconds) > maxDuration {
return c.denyCSR(ctx, req, fmt.Sprintf("CSR %q was created with invalid expirationSeconds value: %d", req.Name, *req.Spec.ExpirationSeconds))
}
return c.approveCSR(ctx, req)
}
// CSR specific functions
func (c *CertController) filterCSR(csr *certificatesv1.CertificateSigningRequest) bool {
nsName := types.NamespacedName{Namespace: csr.Namespace, Name: csr.Name}
csrPEM, _ := pem.Decode(csr.Spec.Request)
if csrPEM == nil {
klog.Errorf("Failed to PEM-parse the CSR block in .spec.request: no CSRs were found in %s", nsName)
return false
}
x509CSR, err := x509.ParseCertificateRequest(csrPEM.Bytes)
if err != nil {
klog.Errorf("Failed to parse the CSR .spec.request of %q: %v", nsName, err)
return false
}
return strings.HasPrefix(x509CSR.Subject.CommonName, c.commonNamePrefixes) &&
csr.Spec.SignerName == certificatesv1.KubeAPIServerClientSignerName
}
func (c *CertController) approveCSR(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) error {
csr.Status.Conditions = append(csr.Status.Conditions,
certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
Reason: "AutoApproved",
Message: fmt.Sprintf("Auto-approved CSR %q", csr.Name),
})
c.recorder.Eventf(csr, corev1.EventTypeNormal, "CSRApproved", "CSR %q has been approved by %s", csr.Name, ControllerName)
_, err := c.clientset.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, csr.Name, csr, metav1.UpdateOptions{})
return err
}
func (c *CertController) denyCSR(ctx context.Context, csr *certificatesv1.CertificateSigningRequest, message string) error {
csr.Status.Conditions = append(csr.Status.Conditions,
certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateDenied,
Status: corev1.ConditionTrue,
Reason: "CSRDenied",
Message: message,
},
)
c.recorder.Eventf(csr, corev1.EventTypeWarning, "CSRDenied", "The CSR %q has been denied by: %s", csr.Name, ControllerName, message)
_, err := c.clientset.CertificatesV1().CertificateSigningRequests().Update(ctx, csr, metav1.UpdateOptions{})
return err
}
func isApprovedOrDenied(status *certificatesv1.CertificateSigningRequestStatus) bool {
for _, c := range status.Conditions {
if c.Type == certificatesv1.CertificateApproved || c.Type == certificatesv1.CertificateDenied {
return true
}
}
return false
}
func main() {
klog.Infof("starting cert-approver")
// Start watching for pod creations
certController, err := NewCertController()
if err != nil {
klog.Fatal(err)
}
stopCh := make(chan struct{})
defer close(stopCh)
go certController.Run(stopCh)
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
<-sigterm
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2023 Multus 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.
// This is a install tool for multus plugins
package main
import (
"fmt"
"os"
"github.com/spf13/pflag"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/cmdutils"
)
func main() {
typeFlag := pflag.StringP("type", "t", "", "specify installer type (thick/thin)")
destDir := pflag.StringP("dest-dir", "d", "/host/opt/cni/bin", "destination directory")
helpFlag := pflag.BoolP("help", "h", false, "show help message and quit")
pflag.Parse()
if *helpFlag {
pflag.PrintDefaults()
os.Exit(1)
}
multusFileName := ""
switch *typeFlag {
case "thick":
multusFileName = "multus-shim"
case "thin":
multusFileName = "multus"
default:
fmt.Fprintf(os.Stderr, "--type is missing or --type has invalid value\n")
os.Exit(1)
}
err := cmdutils.CopyFileAtomic(fmt.Sprintf("/usr/src/multus-cni/bin/%s", multusFileName), *destDir, fmt.Sprintf("%s.temp", multusFileName), multusFileName)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to copy file %s: %v\n", multusFileName, err)
os.Exit(1)
}
fmt.Printf("multus %s copy succeeded!\n", multusFileName)
}

View File

@@ -0,0 +1,145 @@
// Copyright (c) 2023 Multus 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.
// This binary submit CSR for kube controll access for multus thin plugin
// and generate Kubeconfig
package main
import (
"encoding/base64"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
"text/template"
"time"
"github.com/spf13/pflag"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/k8sclient"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
)
var kubeConfigTemplate = `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: {{.CADATA}}
server: {{.K8S_APISERVER}}
name: default-cluster
contexts:
- context:
cluster: default-cluster
namespace: default
user: default-auth
name: default-context
current-context: default-context
kind: Config
preferences: {}
users:
- name: default-auth
user:
client-certificate: {{.CERTDIR}}/multus-client-current.pem
client-key: {{.CERTDIR}}/multus-client-current.pem
`
func main() {
certDir := pflag.StringP("certdir", "", "/tmp", "specify cert directory")
bootstrapConfig := pflag.StringP("bootstrap-config", "", "/tmp/kubeconfig", "specify bootstrap kubernetes config")
kubeconfigPathRaw := pflag.StringP("kubeconfig", "", "/run/multus/kubeconfig", "specify output kubeconfig path")
certDurationString := pflag.StringP("cert-duration", "", "10m", "specify certificate duration")
helpFlag := pflag.BoolP("help", "h", false, "show help message and quit")
kubeconfigPath, err := filepath.Abs(*kubeconfigPathRaw)
if err != nil {
klog.Fatalf("illegal path %s in kubeconfigPath %s: %v", kubeconfigPath, *kubeconfigPathRaw, err)
}
pflag.Parse()
if *helpFlag {
pflag.PrintDefaults()
os.Exit(1)
}
// check variables
if _, err := os.Stat(*bootstrapConfig); err != nil {
klog.Fatalf("failed to read bootstrap config %q", *bootstrapConfig)
}
st, err := os.Stat(*certDir)
if err != nil {
klog.Fatalf("failed to find cert directory %q", *certDir)
}
if !st.IsDir() {
klog.Fatalf("cert directory %q is not directory", *certDir)
}
certDuration, err := time.ParseDuration(*certDurationString)
if err != nil {
klog.Fatalf("failed to parse duration %q: %v", *certDurationString, err)
}
nodeName := os.Getenv("MULTUS_NODE_NAME")
if nodeName == "" {
klog.Fatalf("cannot identify node name from MULTUS_NODE_NAME env variables")
}
// retrieve API server from bootstrapConfig()
config, err := clientcmd.BuildConfigFromFlags("", *bootstrapConfig)
if err != nil {
klog.Fatalf("cannot get in-cluster config: %v", err)
}
apiServer := fmt.Sprintf("%s%s", config.Host, config.APIPath)
caData := base64.StdEncoding.EncodeToString(config.CAData)
// run certManager to create certification
if _, err = k8sclient.PerNodeK8sClient(nodeName, *bootstrapConfig, certDuration, *certDir); err != nil {
klog.Fatalf("failed to start cert manager: %v", err)
}
fp, err := os.OpenFile(kubeconfigPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
klog.Fatalf("cannot create kubeconfig file %q: %v", kubeconfigPath, err)
}
// render kubeconfig
templateKubeconfig, err := template.New("kubeconfig").Parse(kubeConfigTemplate)
if err != nil {
klog.Fatalf("template parse error: %v", err)
}
templateData := map[string]string{
"CADATA": caData,
"CERTDIR": *certDir,
"K8S_APISERVER": apiServer,
}
// genearate kubeconfig from template
if err = templateKubeconfig.Execute(fp, templateData); err != nil {
klog.Fatalf("cannot create kubeconfig: %v", err)
}
if err = fp.Close(); err != nil {
klog.Fatalf("cannot save kubeconfig: %v", err)
}
klog.Infof("kubeconfig %q is saved", kubeconfigPath)
// wait for signal
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
<-sigterm
klog.Infof("signal received. remove kubeconfig %q and quit.", kubeconfigPath)
err = os.Remove(kubeconfigPath)
if err != nil {
klog.Errorf("failed to remove kubeconfig %q: %v", kubeconfigPath, err)
}
}

219
cmd/multus-daemon/main.go Normal file
View File

@@ -0,0 +1,219 @@
// Copyright (c) 2021 Multus 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.
// This binary works as a server that receives requests from multus-shim
// CNI plugin and creates network interface for kubernets pods.
package main
import (
"context"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"os/user"
"path/filepath"
"sync"
"syscall"
utilwait "k8s.io/apimachinery/pkg/util/wait"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus"
srv "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/server"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/server/api"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/server/config"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// keep in command line option
version := flag.Bool("version", false, "Show version")
configFilePath := flag.String("config", srv.DefaultMultusDaemonConfigFile, "Specify the path to the multus-daemon configuration")
flag.Parse()
if *version {
fmt.Printf("multus-daemon: %s\n", multus.PrintVersionString())
os.Exit(4)
}
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
daemonConf, err := cniServerConfig(*configFilePath)
if err != nil {
os.Exit(1)
}
multusConf, err := config.ParseMultusConfig(*configFilePath)
if err != nil {
logging.Panicf("startMultusDaemon failed to load the multus configuration: %v", err)
os.Exit(1)
}
logging.Verbosef("multus-daemon started")
if multusConf.ReadinessIndicatorFile != "" {
// Check readinessindicator file before daemon launch
logging.Verbosef("Readiness Indicator file check")
if err := types.GetReadinessIndicatorFile(multusConf.ReadinessIndicatorFile); err != nil {
_ = logging.Errorf("have you checked that your default network is ready? still waiting for readinessindicatorfile @ %v. pollimmediate error: %v", multusConf.ReadinessIndicatorFile, err)
os.Exit(1)
}
logging.Verbosef("Readiness Indicator file check done!")
}
var configManager *config.Manager
var ignoreReadinessIndicator bool
if multusConf.MultusConfigFile == "auto" {
if multusConf.CNIVersion == "" {
_ = logging.Errorf("the CNI version is a mandatory parameter when the '-multus-config-file=auto' option is used")
}
// Generate multus CNI config from current CNI config
configManager, err = config.NewManager(*multusConf)
if err != nil {
_ = logging.Errorf("failed to create the configuration manager for the primary CNI plugin: %v", err)
os.Exit(2)
}
// ConfigManager watches the readiness indicator file (if configured)
// and exits the daemon when that is removed. The CNIServer does
// not need to re-do that check every CNI operation
ignoreReadinessIndicator = true
} else {
if err := copyUserProvidedConfig(multusConf.MultusConfigFile, multusConf.CniConfigDir); err != nil {
logging.Errorf("failed to copy the user provided configuration %s: %v", multusConf.MultusConfigFile, err)
}
}
if err := startMultusDaemon(ctx, daemonConf, ignoreReadinessIndicator); err != nil {
logging.Panicf("failed start the multus thick-plugin listener: %v", err)
os.Exit(3)
}
// Wait until daemon ready
logging.Verbosef("API readiness check")
if api.WaitUntilAPIReady(daemonConf.SocketDir) != nil {
logging.Panicf("failed to ready multus-daemon socket: %v", err)
os.Exit(1)
}
logging.Verbosef("API readiness check done!")
signalCh := make(chan os.Signal, 16)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
for sig := range signalCh {
logging.Verbosef("caught %v, stopping...", sig)
cancel()
}
}()
var wg sync.WaitGroup
if configManager != nil {
if err := configManager.Start(ctx, &wg); err != nil {
_ = logging.Errorf("failed to start config manager: %v", err)
os.Exit(3)
}
}
wg.Wait()
logging.Verbosef("multus daemon is exited")
}
func startMultusDaemon(ctx context.Context, daemonConfig *srv.ControllerNetConf, ignoreReadinessIndicator bool) error {
if user, err := user.Current(); err != nil || user.Uid != "0" {
return fmt.Errorf("failed to run multus-daemon with root: %v, now running in uid: %s", err, user.Uid)
}
if err := srv.FilesystemPreRequirements(daemonConfig.SocketDir); err != nil {
return fmt.Errorf("failed to prepare the cni-socket for communicating with the shim: %w", err)
}
server, err := srv.NewCNIServer(daemonConfig, daemonConfig.ConfigFileContents, ignoreReadinessIndicator)
if err != nil {
return fmt.Errorf("failed to create the server: %v", err)
}
if daemonConfig.MetricsPort != nil {
go utilwait.UntilWithContext(ctx, func(_ context.Context) {
http.Handle("/metrics", promhttp.Handler())
logging.Debugf("metrics port: %d", *daemonConfig.MetricsPort)
logging.Debugf("metrics: %s", http.ListenAndServe(fmt.Sprintf(":%d", *daemonConfig.MetricsPort), nil))
}, 0)
}
l, err := srv.GetListener(api.SocketPath(daemonConfig.SocketDir))
if err != nil {
return fmt.Errorf("failed to start the CNI server using socket %s. Reason: %+v", api.SocketPath(daemonConfig.SocketDir), err)
}
server.Start(ctx, l)
go func() {
<-ctx.Done()
server.Shutdown(context.Background())
}()
return nil
}
func cniServerConfig(configFilePath string) (*srv.ControllerNetConf, error) {
path, err := filepath.Abs(configFilePath)
if err != nil {
return nil, fmt.Errorf("illegal path %s in server config path %s: %w", path, configFilePath, err)
}
configFileContents, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return srv.LoadDaemonNetConf(configFileContents)
}
func copyUserProvidedConfig(multusConfigPath string, cniConfigDir string) error {
path, err := filepath.Abs(multusConfigPath)
if err != nil {
return fmt.Errorf("illegal path %s in multusConfigPath %s: %w", path, multusConfigPath, err)
}
srcFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open (READ only) file %s: %w", path, err)
}
dstFileName := cniConfigDir + "/" + filepath.Base(multusConfigPath)
dstFile, err := os.Create(dstFileName)
if err != nil {
return fmt.Errorf("creating copying file %s: %w", dstFileName, err)
}
nBytes, err := io.Copy(dstFile, srcFile)
if err != nil {
return fmt.Errorf("error copying file: %w", err)
}
srcFileInfo, err := srcFile.Stat()
if err != nil {
return fmt.Errorf("failed to stat the file: %w", err)
} else if nBytes != srcFileInfo.Size() {
return fmt.Errorf("error copying file - copied only %d bytes out of %d", nBytes, srcFileInfo.Size())
}
return nil
}

58
cmd/multus-shim/main.go Normal file
View File

@@ -0,0 +1,58 @@
// Copyright (c) 2022 Multus 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.
// This is a "Multi-plugin".The delegate concept referred from CNI project
// It reads other plugin netconf, and then invoke them, e.g.
// flannel or sriov plugin.
package main
import (
"flag"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/skel"
cniversion "github.com/containernetworking/cni/pkg/version"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/server/api"
)
func main() {
// Init command line flags to clear vendored packages' one, especially in init()
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
// add version flag
versionOpt := false
flag.BoolVar(&versionOpt, "version", false, "Show application version")
flag.BoolVar(&versionOpt, "v", false, "Show application version")
flag.Parse()
if versionOpt {
fmt.Printf("multus-shim: %s\n", multus.PrintVersionString())
return
}
skel.PluginMain(
func(args *skel.CmdArgs) error {
return api.CmdAdd(args)
},
func(args *skel.CmdArgs) error {
return api.CmdCheck(args)
},
func(args *skel.CmdArgs) error {
return api.CmdDel(args)
},
cniversion.All, "meta-plugin that delegates to other CNI plugins")
}

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2017 Intel Corporation
// Copyright (c) 2016 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,7 +16,6 @@
// This is a "Multi-plugin".The delegate concept referred from CNI project
// It reads other plugin netconf, and then invoke them, e.g.
// flannel or sriov plugin.
package main
import (
@@ -25,7 +25,7 @@ import (
"github.com/containernetworking/cni/pkg/skel"
cniversion "github.com/containernetworking/cni/pkg/version"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/multus"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus"
)
func main() {
@@ -38,8 +38,8 @@ func main() {
flag.BoolVar(&versionOpt, "version", false, "Show application version")
flag.BoolVar(&versionOpt, "v", false, "Show application version")
flag.Parse()
if versionOpt == true {
fmt.Printf("%s\n", multus.PrintVersionString())
if versionOpt {
fmt.Printf("multus: %s\n", multus.PrintVersionString())
return
}

683
cmd/thin_entrypoint/main.go Normal file
View File

@@ -0,0 +1,683 @@
// Copyright (c) 2023 Multus 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.
// This is a entrypoint for thin (stand-alone) images.
package main
import (
"bytes"
"crypto/sha256"
b64 "encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/containernetworking/cni/libcni"
"github.com/spf13/pflag"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/cmdutils"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/signals"
)
// Options stores command line options
type Options struct {
CNIBinDir string
CNIConfDir string
CNIVersion string
MultusConfFile string
MultusBinFile string // may be hidden or remove?
MultusCNIConfDir string
SkipMultusBinaryCopy bool
MultusKubeConfigFileHost string
MultusMasterCNIFileName string
NamespaceIsolation bool
GlobalNamespaces string
MultusAutoconfigDir string
MultusLogToStderr bool
MultusLogLevel string
MultusLogFile string
OverrideNetworkName bool
CleanupConfigOnExit bool
RenameConfFile bool
ReadinessIndicatorFile string
AdditionalBinDir string
ForceCNIVersion bool
SkipTLSVerify bool
SkipMultusConfWatch bool
}
const (
serviceAccountTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccountCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
func (o *Options) addFlags() {
pflag.ErrHelp = nil // suppress error message for help
fs := pflag.CommandLine
fs.StringVar(&o.CNIBinDir, "cni-bin-dir", "/host/opt/cni/bin", "CNI binary directory")
fs.StringVar(&o.CNIConfDir, "cni-conf-dir", "/host/etc/cni/net.d", "CNI config directory")
fs.StringVar(&o.CNIVersion, "cni-version", "", "CNI version for multus CNI config (e.g. '0.3.1')")
fs.StringVar(&o.MultusConfFile, "multus-conf-file", "auto", "multus CNI config file")
fs.StringVar(&o.MultusBinFile, "multus-bin-file", "/usr/src/multus-cni/bin/multus", "multus binary file path")
fs.StringVar(&o.MultusCNIConfDir, "multus-cni-conf-dir", "/host/etc/cni/multus/net.d", "multus specific CNI config directory")
fs.BoolVar(&o.SkipMultusBinaryCopy, "skip-multus-binary-copy", false, "skip multus binary file copy")
fs.StringVar(&o.MultusKubeConfigFileHost, "multus-kubeconfig-file-host", "/etc/cni/net.d/multus.d/multus.kubeconfig", "kubeconfig for multus (used only with --multus-conf-file=auto)")
fs.StringVar(&o.MultusMasterCNIFileName, "multus-master-cni-file-name", "", "master CNI file in multus-autoconfig-dir")
fs.BoolVar(&o.NamespaceIsolation, "namespace-isolation", false, "namespace isolation")
fs.StringVar(&o.GlobalNamespaces, "global-namespaces", "", "global namespaces, comma separated (used only with --namespace-isolation=true)")
fs.StringVar(&o.MultusAutoconfigDir, "multus-autoconfig-dir", "/host/etc/cni/net.d", "multus autoconfig dir (used only with --multus-conf-file=auto)")
fs.BoolVar(&o.MultusLogToStderr, "multus-log-to-stderr", true, "log to stderr")
fs.StringVar(&o.MultusLogLevel, "multus-log-level", "", "multus log level")
fs.StringVar(&o.MultusLogFile, "multus-log-file", "", "multus log file")
fs.BoolVar(&o.OverrideNetworkName, "override-network-name", false, "override network name from master cni file (used only with --multus-conf-file=auto)")
fs.BoolVar(&o.CleanupConfigOnExit, "cleanup-config-on-exit", false, "cleanup config file on exit")
fs.BoolVar(&o.SkipMultusConfWatch, "skip-config-watch", false, "dont watch for config (master cni and kubeconfig) changes (used only with --multus-conf-file=auto)")
fs.BoolVar(&o.RenameConfFile, "rename-conf-file", false, "rename master config file to invalidate (used only with --multus-conf-file=auto)")
fs.StringVar(&o.ReadinessIndicatorFile, "readiness-indicator-file", "", "readiness indicator file (used only with --multus-conf-file=auto)")
fs.StringVar(&o.AdditionalBinDir, "additional-bin-dir", "", "adds binDir option to configuration (used only with --multus-conf-file=auto)")
fs.BoolVar(&o.SkipTLSVerify, "skip-tls-verify", false, "skip TLS verify")
fs.BoolVar(&o.ForceCNIVersion, "force-cni-version", false, "force cni version to '--cni-version' (only for e2e-kind testing)")
fs.MarkHidden("force-cni-version")
fs.MarkHidden("skip-tls-verify")
}
func (o *Options) verifyFileExists() error {
// CNIConfDir
if _, err := os.Stat(o.CNIConfDir); err != nil {
return fmt.Errorf("cni-conf-dir is not found: %v", err)
}
// CNIBinDir
if _, err := os.Stat(o.CNIBinDir); err != nil {
return fmt.Errorf("cni-bin-dir is not found: %v", err)
}
// MultusBinFile
if _, err := os.Stat(o.MultusBinFile); err != nil {
return fmt.Errorf("multus-bin-file is not found: %v", err)
}
if o.MultusConfFile != "auto" {
// MultusConfFile
if _, err := os.Stat(o.MultusConfFile); err != nil {
return fmt.Errorf("multus-conf-file is not found: %v", err)
}
}
return nil
}
const kubeConfigTemplate = `# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: {{ .KubeConfigHost }}
{{ .KubeServerTLS }}
users:
- name: multus
user:
token: "{{ .KubeServiceAccountToken }}"
contexts:
- name: multus-context
context:
cluster: local
user: multus
current-context: multus-context
`
func getFileAndHash(filepath string) ([]byte, []byte, error) {
if _, err := os.Stat(filepath); err != nil {
return nil, nil, fmt.Errorf("file %s not found: %v", filepath, err)
}
content, err := os.ReadFile(filepath)
if err != nil {
return nil, nil, fmt.Errorf("cannot read %s file: %v", filepath, err)
}
hash := sha256.New()
hash.Write(content)
return content, hash.Sum(nil), nil
}
func (o *Options) createKubeConfig(prevCAHash, prevSATokenHash []byte) ([]byte, []byte, error) {
caFileByte, caHash, err := getFileAndHash(serviceAccountCAFile)
if err != nil {
return nil, nil, err
}
saTokenByte, saTokenHash, err := getFileAndHash(serviceAccountTokenFile)
if err != nil {
return nil, nil, err
}
caUnchanged := prevCAHash != nil && bytes.Equal(prevCAHash, caHash)
saUnchanged := prevSATokenHash != nil && bytes.Equal(prevSATokenHash, saTokenHash)
if o.SkipTLSVerify {
if saUnchanged {
return caHash, saTokenHash, nil
}
} else {
if caUnchanged && saUnchanged {
return caHash, saTokenHash, nil
}
}
if prevSATokenHash != nil {
// don't log "recreating" on first function execution
fmt.Printf("CA (%v) or SA token (%v) changed - recreating kubeconfig\n", !caUnchanged, !saUnchanged)
}
// create multus.d directory
if err := os.MkdirAll(fmt.Sprintf("%s/multus.d", o.CNIConfDir), 0755); err != nil {
return nil, nil, fmt.Errorf("cannot create multus.d directory: %v", err)
}
// create multus cni conf directory
if err := os.MkdirAll(o.MultusCNIConfDir, 0755); err != nil {
return nil, nil, fmt.Errorf("cannot create multus-cni-conf-dir(%s) directory: %v", o.MultusCNIConfDir, err)
}
// get Kubernetes service protocol/host/port
kubeProtocol := os.Getenv("KUBERNETES_SERVICE_PROTOCOL")
if kubeProtocol == "" {
kubeProtocol = "https"
}
kubeHost := os.Getenv("KUBERNETES_SERVICE_HOST")
kubePort := os.Getenv("KUBERNETES_SERVICE_PORT")
// check tlsConfig
tlsConfig := ""
if o.SkipTLSVerify {
tlsConfig = "insecure-skip-tls-verify: true"
} else {
// create tlsConfig by service account CA file
caFileB64 := bytes.ReplaceAll([]byte(b64.StdEncoding.EncodeToString(caFileByte)), []byte("\n"), []byte(""))
tlsConfig = fmt.Sprintf("certificate-authority-data: %s", string(caFileB64))
}
// create kubeconfig by template and replace it by atomic
tempKubeConfigFile := fmt.Sprintf("%s/multus.d/multus.kubeconfig.new", o.CNIConfDir)
multusKubeConfig := fmt.Sprintf("%s/multus.d/multus.kubeconfig", o.CNIConfDir)
fp, err := os.OpenFile(tempKubeConfigFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return nil, nil, fmt.Errorf("cannot create kubeconfig temp file: %v", err)
}
templateKubeconfig, err := template.New("kubeconfig").Parse(kubeConfigTemplate)
if err != nil {
return nil, nil, fmt.Errorf("template parse error: %v", err)
}
templateData := map[string]string{
"KubeConfigHost": fmt.Sprintf("%s://[%s]:%s", kubeProtocol, kubeHost, kubePort),
"KubeServerTLS": tlsConfig,
"KubeServiceAccountToken": string(saTokenByte),
}
// generate kubeconfig from template
if err = templateKubeconfig.Execute(fp, templateData); err != nil {
return nil, nil, fmt.Errorf("cannot create kubeconfig: %v", err)
}
if err := fp.Sync(); err != nil {
os.Remove(fp.Name())
return nil, nil, fmt.Errorf("cannot flush kubeconfig temp file: %v", err)
}
if err := fp.Close(); err != nil {
os.Remove(fp.Name())
return nil, nil, fmt.Errorf("cannot close kubeconfig temp file: %v", err)
}
// replace file with tempfile
if err := os.Rename(tempKubeConfigFile, multusKubeConfig); err != nil {
return nil, nil, fmt.Errorf("cannot replace %q with temp file %q: %v", multusKubeConfig, tempKubeConfigFile, err)
}
fmt.Printf("kubeconfig is created in %s\n", multusKubeConfig)
return caHash, saTokenHash, nil
}
const multusConflistTemplate = `{
"cniVersion": "{{ .CNIVersion }}",
"name": "{{ .MasterPluginNetworkName }}",
"plugins": [ {
"type": "multus",{{
.NestedCapabilities
}}{{
.NamespaceIsolationConfig
}}{{
.GlobalNamespacesConfig
}}{{
.LogToStderrConfig
}}{{
.LogLevelConfig
}}{{
.LogFileConfig
}}{{
.AdditionalBinDirConfig
}}{{
.MultusCNIConfDirConfig
}}{{
.ReadinessIndicatorFileConfig
}}
"kubeconfig": "{{ .MultusKubeConfigFileHost }}",
"delegates": [
{{ .MasterPluginJSON }}
]
}]
}
`
const multusConfTemplate = `{
"cniVersion": "{{ .CNIVersion }}",
"name": "{{ .MasterPluginNetworkName }}",
"type": "multus",{{
.NestedCapabilities
}}{{
.NamespaceIsolationConfig
}}{{
.GlobalNamespacesConfig
}}{{
.LogToStderrConfig
}}{{
.LogLevelConfig
}}{{
.LogFileConfig
}}{{
.AdditionalBinDirConfig
}}{{
.MultusCNIConfDirConfig
}}{{
.ReadinessIndicatorFileConfig
}}
"kubeconfig": "{{ .MultusKubeConfigFileHost }}",
"delegates": [
{{ .MasterPluginJSON }}
]
}
`
func (o *Options) getMasterConfigPath() (string, error) {
// Master config file is specified
if o.MultusMasterCNIFileName != "" {
return filepath.Join(o.MultusAutoconfigDir, o.MultusMasterCNIFileName), nil
}
// Pick the alphabetically first config file from MultusAutoconfigDir
files, err := libcni.ConfFiles(o.MultusAutoconfigDir, []string{".conf", ".conflist"})
if err != nil {
return "", fmt.Errorf("cannot find master CNI config in %q: %v", o.MultusAutoconfigDir, err)
}
for _, filename := range files {
if !strings.HasPrefix(filepath.Base(filename), "00-multus.conf") {
return filename, nil
}
}
// No config file found
return "", fmt.Errorf("cannot find valid master CNI config in %q", o.MultusAutoconfigDir)
}
func (o *Options) createMultusConfig(prevMasterConfigFileHash []byte) (string, []byte, error) {
masterConfigPath, err := o.getMasterConfigPath()
if err != nil {
return "", nil, err
}
masterConfigBytes, masterConfigFileHash, err := getFileAndHash(masterConfigPath)
if err != nil {
return "", nil, err
}
if prevMasterConfigFileHash != nil && bytes.Equal(prevMasterConfigFileHash, masterConfigFileHash) {
return masterConfigPath, masterConfigFileHash, nil
}
if prevMasterConfigFileHash != nil {
// don't log "recreating" on first function execution
fmt.Printf("master config changed - recreating multus config\n")
}
masterConfig := map[string]interface{}{}
if err = json.Unmarshal(masterConfigBytes, &masterConfig); err != nil {
return "", nil, fmt.Errorf("cannot read master CNI config json: %v", err)
}
// check CNIVersion
masterCNIVersionElem, ok := masterConfig["cniVersion"]
if !ok {
return "", nil, fmt.Errorf("cannot get cniVersion in master CNI config file %q: %v", masterConfigPath, err)
}
if o.ForceCNIVersion {
masterConfig["cniVersion"] = o.CNIVersion
fmt.Printf("force CNI version to %q\n", o.CNIVersion)
} else {
masterCNIVersion := masterCNIVersionElem.(string)
if o.CNIVersion != "" && masterCNIVersion != o.CNIVersion {
return "", nil, fmt.Errorf("Multus cni version is %q while master plugin cni version is %q", o.CNIVersion, masterCNIVersion)
}
o.CNIVersion = masterCNIVersion
}
cniVersionConfig := o.CNIVersion
// check OverrideNetworkName (if true, get master plugin name, otherwise 'multus-cni-network'
masterPluginNetworkName := "multus-cni-network"
if o.OverrideNetworkName {
masterPluginNetworkElem, ok := masterConfig["name"]
if !ok {
return "", nil, fmt.Errorf("cannot get name in master CNI config file %q: %v", masterConfigPath, err)
}
masterPluginNetworkName = masterPluginNetworkElem.(string)
fmt.Printf("master plugin name is overrided to %q\n", masterPluginNetworkName)
}
// check capabilities (from master conf, top and 'plugins')
masterCapabilities := map[string]bool{}
_, isMasterConfList := masterConfig["plugins"]
if isMasterConfList {
masterPluginsElem, ok := masterConfig["plugins"]
if !ok {
return "", nil, fmt.Errorf("cannot get 'plugins' field in master CNI config file %q: %v", masterConfigPath, err)
}
masterPlugins := masterPluginsElem.([]interface{})
for _, v := range masterPlugins {
pluginFields := v.(map[string]interface{})
capabilitiesElem, ok := pluginFields["capabilities"]
if ok {
capabilities := capabilitiesElem.(map[string]interface{})
for k, v := range capabilities {
masterCapabilities[k] = v.(bool)
}
}
}
fmt.Printf("master capabilities is get from conflist\n")
} else {
masterCapabilitiesElem, ok := masterConfig["capabilities"]
if ok {
for k, v := range masterCapabilitiesElem.(map[string]interface{}) {
masterCapabilities[k] = v.(bool)
}
}
fmt.Printf("master capabilities is get from conffile\n")
}
nestedCapabilitiesConf := ""
if len(masterCapabilities) != 0 {
capabilitiesByte, err := json.Marshal(masterCapabilities)
if err != nil {
return "", nil, fmt.Errorf("cannot get capabilities map: %v", err)
}
nestedCapabilitiesConf = fmt.Sprintf("\n \"capabilities\": %s,", string(capabilitiesByte))
}
// check NamespaceIsolation
namespaceIsolationConfig := ""
if o.NamespaceIsolation {
namespaceIsolationConfig = "\n \"namespaceIsolation\": true,"
}
// check GlobalNamespaces
globalNamespaceConfig := ""
if o.GlobalNamespaces != "" {
globalNamespaceConfig = fmt.Sprintf("\n \"globalNamespaces\": %q,", o.GlobalNamespaces)
}
// check MultusLogToStderr
logToStderrConfig := ""
if !o.MultusLogToStderr {
logToStderrConfig = "\n \"logToStderr\": false,"
}
// check MultusLogLevel (debug/error/panic/verbose) and reject others
logLevelConfig := ""
logLevelStr := strings.ToLower(o.MultusLogLevel)
switch logLevelStr {
case "debug", "error", "panic", "verbose":
logLevelConfig = fmt.Sprintf("\n \"logLevel\": %q,", logLevelStr)
case "":
// no logLevel config, skipped
default:
return "", nil, fmt.Errorf("Log levels should be one of: debug/verbose/error/panic, did not understand: %q", o.MultusLogLevel)
}
// check MultusLogFile
logFileConfig := ""
if o.MultusLogFile != "" {
logFileConfig = fmt.Sprintf("\n \"logFile\": %q,", o.MultusLogFile)
}
// check AdditionalBinDir
additionalBinDirConfig := ""
if o.AdditionalBinDir != "" {
additionalBinDirConfig = fmt.Sprintf("\n \"binDir\": %q,", o.AdditionalBinDir)
}
// check MultusCNIConfDir
multusCNIConfDirConfig := ""
if o.MultusCNIConfDir != "" {
multusCNIConfDirConfig = fmt.Sprintf("\n \"cniConf\": %q,", o.MultusCNIConfDir)
}
// check ReadinessIndicatorFile
readinessIndicatorFileConfig := ""
if o.ReadinessIndicatorFile != "" {
readinessIndicatorFileConfig = fmt.Sprintf("\n \"readinessindicatorfile\": %q,", o.ReadinessIndicatorFile)
}
// fill .MasterPluginJSON
masterPluginByte, err := json.Marshal(masterConfig)
if err != nil {
return "", nil, fmt.Errorf("cannot encode master CNI config: %v", err)
}
// generate multus config
tempFileName := fmt.Sprintf("%s/00-multus.conf.new", o.CNIConfDir)
fp, err := os.OpenFile(tempFileName, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return "", nil, fmt.Errorf("cannot create multus cni temp file: %v", err)
}
// use conflist template if cniVersionConfig == "1.0.0"
multusConfFilePath := fmt.Sprintf("%s/00-multus.conf", o.CNIConfDir)
templateMultusConfig, err := template.New("multusCNIConfig").Parse(multusConfTemplate)
if err != nil {
return "", nil, fmt.Errorf("template parse error: %v", err)
}
if o.CNIVersion == "1.0.0" { //Check 1.0.0 or above!
multusConfFilePath = fmt.Sprintf("%s/00-multus.conflist", o.CNIConfDir)
templateMultusConfig, err = template.New("multusCNIConfig").Parse(multusConflistTemplate)
if err != nil {
return "", nil, fmt.Errorf("template parse error: %v", err)
}
}
templateData := map[string]string{
"CNIVersion": cniVersionConfig,
"MasterPluginNetworkName": masterPluginNetworkName,
"NestedCapabilities": nestedCapabilitiesConf,
"NamespaceIsolationConfig": namespaceIsolationConfig,
"GlobalNamespacesConfig": globalNamespaceConfig,
"LogToStderrConfig": logToStderrConfig,
"LogLevelConfig": logLevelConfig,
"LogFileConfig": logFileConfig,
"AdditionalBinDirConfig": additionalBinDirConfig,
"MultusCNIConfDirConfig": multusCNIConfDirConfig,
"ReadinessIndicatorFileConfig": readinessIndicatorFileConfig,
"MultusKubeConfigFileHost": o.MultusKubeConfigFileHost, // be fixed?
"MasterPluginJSON": string(masterPluginByte),
}
if err = templateMultusConfig.Execute(fp, templateData); err != nil {
return "", nil, fmt.Errorf("cannot create multus cni config: %v", err)
}
if err := fp.Sync(); err != nil {
os.Remove(tempFileName)
return "", nil, fmt.Errorf("cannot flush multus cni config: %v", err)
}
if err := fp.Close(); err != nil {
os.Remove(tempFileName)
return "", nil, fmt.Errorf("cannot close multus cni config: %v", err)
}
if err := os.Rename(tempFileName, multusConfFilePath); err != nil {
return "", nil, fmt.Errorf("cannot replace %q with temp file %q: %v", multusConfFilePath, tempFileName, err)
}
if o.RenameConfFile {
//masterConfigPath
renamedMasterConfigPath := fmt.Sprintf("%s.old", masterConfigPath)
if err := os.Rename(masterConfigPath, renamedMasterConfigPath); err != nil {
return "", nil, fmt.Errorf("cannot move original master file to %q", renamedMasterConfigPath)
}
fmt.Printf("Original master file moved to %q\n", renamedMasterConfigPath)
}
return masterConfigPath, masterConfigFileHash, nil
}
func main() {
opt := Options{}
opt.addFlags()
helpFlag := pflag.BoolP("help", "h", false, "show help message and quit")
pflag.Parse()
if *helpFlag {
pflag.PrintDefaults()
os.Exit(1)
}
err := opt.verifyFileExists()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return
}
// copy multus binary
if !opt.SkipMultusBinaryCopy {
// Copy
if err = cmdutils.CopyFileAtomic(opt.MultusBinFile, opt.CNIBinDir, "_multus", "multus"); err != nil {
fmt.Fprintf(os.Stderr, "failed at multus copy: %v\n", err)
return
}
}
var masterConfigHash, caHash, saTokenHash []byte
var masterConfigFilePath string
// copy user specified multus conf to CNI conf directory
if opt.MultusConfFile != "auto" {
caHash, saTokenHash, err = opt.createKubeConfig(nil, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create multus kubeconfig: %v\n", err)
return
}
confFileName := filepath.Base(opt.MultusConfFile)
tempConfFileName := fmt.Sprintf("%s.temp", confFileName)
if err = cmdutils.CopyFileAtomic(opt.MultusConfFile, opt.CNIConfDir, tempConfFileName, confFileName); err != nil {
fmt.Fprintf(os.Stderr, "failed at copy multus conf file: %v\n", err)
return
}
fmt.Printf("multus config file %s is copied.\n", opt.MultusConfFile)
} else { // auto generate multus config
caHash, saTokenHash, err = opt.createKubeConfig(nil, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create multus kubeconfig: %v\n", err)
return
}
fmt.Printf("kubeconfig file is created.\n")
masterConfigFilePath, masterConfigHash, err = opt.createMultusConfig(nil)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create multus config: %v\n", err)
return
}
fmt.Printf("multus config file is created.\n")
}
ctx := signals.SetupSignalHandler()
if opt.CleanupConfigOnExit {
defer cleanupMultusConf(&opt)
}
watchChanges := opt.CleanupConfigOnExit && opt.MultusConfFile == "auto" && !opt.SkipMultusConfWatch
if watchChanges {
fmt.Printf("Entering watch loop...\n")
masterConfigExists := true
outer:
for range time.Tick(1 * time.Second) {
select {
case <-ctx.Done():
// signal received break from loop
break outer
default:
// Check kubeconfig and update if different (i.e. service account updated)
caHash, saTokenHash, err = opt.createKubeConfig(caHash, saTokenHash)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to update multus kubeconfig: %v\n", err)
return
}
// TODO: should we watch master CNI config (by fsnotify? https://github.com/fsnotify/fsnotify)
_, err = os.Stat(masterConfigFilePath)
// if masterConfigFilePath is no longer exists
if os.IsNotExist(err) {
if masterConfigExists {
fmt.Printf("Master plugin @ %q has been deleted. waiting for its restoration...\n", masterConfigFilePath)
}
masterConfigExists = false
continue
}
if !masterConfigExists {
fmt.Printf("Master plugin @ %q was restored. Regenerating given configuration.\n", masterConfigFilePath)
masterConfigExists = true
}
masterConfigFilePath, masterConfigHash, err = opt.createMultusConfig(masterConfigHash)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create multus config: %v\n", err)
return
}
}
}
} else {
// wait until signal received
<-ctx.Done()
}
}
func cleanupMultusConf(opt *Options) {
// try remove multus conf
if opt.MultusConfFile == "auto" {
multusConfFilePath := fmt.Sprintf("%s/00-multus.conf", opt.CNIConfDir)
_ = os.Remove(multusConfFilePath)
multusConfFilePath = fmt.Sprintf("%s/00-multus.conflist", opt.CNIConfDir)
_ = os.Remove(multusConfFilePath)
} else {
confFileName := filepath.Base(opt.MultusConfFile)
_ = os.Remove(filepath.Join(opt.CNIConfDir, confFileName))
}
}

View File

@@ -0,0 +1,544 @@
package main
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
. "github.com/onsi/ginkgo/v2" //nolint:golint
. "github.com/onsi/gomega" //nolint:golint
)
// chrootTestHelper performs chroot syscall, returns func to get back to original root or error if occurred
func chrootTestHelper(path string) (func() error, error) {
root, err := os.Open("/")
if err != nil {
return nil, err
}
if err := syscall.Chroot(path); err != nil {
root.Close()
return nil, err
}
return func() error {
defer root.Close()
if err := root.Chdir(); err != nil {
return err
}
return syscall.Chroot(".")
}, nil
}
func TestThinEntrypoint(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "thin_entrypoint")
}
var _ = Describe("thin entrypoint testing", func() {
It("always pass just example", func() {
a := 10
Expect(a).To(Equal(10))
})
It("Run verifyFileExists() with expected environment, autoconfig", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
cniConfDir := fmt.Sprintf("%s/cni_conf_dir", tmpDir)
cniBinDir := fmt.Sprintf("%s/cni_bin_dir", tmpDir)
multusBinFile := fmt.Sprintf("%s/multus_bin", tmpDir)
multusConfFile := fmt.Sprintf("%s/multus_conf", tmpDir)
// CNIConfDir
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// CNIBinDir
Expect(os.Mkdir(cniBinDir, 0755)).To(Succeed())
// MultusBinFile
Expect(os.WriteFile(multusBinFile, nil, 0744)).To(Succeed())
// MultusConfFile
Expect(os.WriteFile(multusConfFile, nil, 0744)).To(Succeed())
err = (&Options{
CNIConfDir: cniConfDir,
CNIBinDir: cniBinDir,
MultusBinFile: multusBinFile,
MultusConfFile: multusConfFile,
}).verifyFileExists()
Expect(err).NotTo(HaveOccurred())
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run verifyFileExists() with invalid environmentMultusConfFile", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
cniConfDir := fmt.Sprintf("%s/cni_conf_dir", tmpDir)
cniBinDir := fmt.Sprintf("%s/cni_bin_dir", tmpDir)
multusBinFile := fmt.Sprintf("%s/multus_bin", tmpDir)
multusConfFile := fmt.Sprintf("%s/multus_conf", tmpDir)
// CNIConfDir
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// CNIBinDir
Expect(os.Mkdir(cniBinDir, 0755)).To(Succeed())
// MultusConfFile
Expect(os.WriteFile(multusConfFile, nil, 0744)).To(Succeed())
err = (&Options{
CNIConfDir: cniConfDir,
CNIBinDir: cniBinDir,
MultusBinFile: multusBinFile,
MultusConfFile: multusConfFile,
}).verifyFileExists()
Expect(err).To(HaveOccurred())
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), default, conf", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfig := `
{
"cniVersion": "0.3.1",
"name": "test1",
"type": "cnitesttype"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/10-testcni.conf", multusAutoConfigDir), []byte(masterCNIConfig), 0755)).To(Succeed())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult := `{
"cniVersion": "0.3.1",
"name": "multus-cni-network",
"type": "multus",
"logToStderr": false,
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"cniVersion":"0.3.1","name":"test1","type":"cnitesttype"}
]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conf", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), capabilities, conf", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfig := `
{
"cniVersion": "0.3.1",
"name": "test1",
"capabilities": { "bandwidth": true },
"type": "cnitesttype"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/10-testcni.conf", multusAutoConfigDir), []byte(masterCNIConfig), 0755)).To(Succeed())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult := `{
"cniVersion": "0.3.1",
"name": "multus-cni-network",
"type": "multus",
"capabilities": {"bandwidth":true},
"logToStderr": false,
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"capabilities":{"bandwidth":true},"cniVersion":"0.3.1","name":"test1","type":"cnitesttype"}
]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conf", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), with options, conf", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfig := `
{
"cniVersion": "0.3.1",
"name": "test1",
"type": "cnitesttype"
}`
err = os.WriteFile(fmt.Sprintf("%s/10-testcni.conf", multusAutoConfigDir), []byte(masterCNIConfig), 0755)
Expect(err).NotTo(HaveOccurred())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
NamespaceIsolation: true,
GlobalNamespaces: "foobar,barfoo",
MultusLogToStderr: true,
MultusLogLevel: "DEBUG",
MultusLogFile: "/tmp/foobar.log",
AdditionalBinDir: "/tmp/add_bin_dir",
MultusCNIConfDir: "/tmp/multus/net.d",
ReadinessIndicatorFile: "/var/lib/foobar_indicator",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult := `{
"cniVersion": "0.3.1",
"name": "multus-cni-network",
"type": "multus",
"namespaceIsolation": true,
"globalNamespaces": "foobar,barfoo",
"logLevel": "debug",
"logFile": "/tmp/foobar.log",
"binDir": "/tmp/add_bin_dir",
"cniConf": "/tmp/multus/net.d",
"readinessindicatorfile": "/var/lib/foobar_indicator",
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"cniVersion":"0.3.1","name":"test1","type":"cnitesttype"}
]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conf", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), default, conflist", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfig := `
{
"cniVersion": "1.0.0",
"name": "test1",
"type": "cnitesttype"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/10-testcni.conf", multusAutoConfigDir), []byte(masterCNIConfig), 0755)).To(Succeed())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult :=
`{
"cniVersion": "1.0.0",
"name": "multus-cni-network",
"plugins": [ {
"type": "multus",
"logToStderr": false,
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"cniVersion":"1.0.0","name":"test1","type":"cnitesttype"}
]
}]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conflist", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), capabilities, conflist", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfig := `
{
"cniVersion": "1.0.0",
"name": "test1",
"capabilities": { "bandwidth": true },
"type": "cnitesttype"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/10-testcni.conflist", multusAutoConfigDir), []byte(masterCNIConfig), 0755)).To(Succeed())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult :=
`{
"cniVersion": "1.0.0",
"name": "multus-cni-network",
"plugins": [ {
"type": "multus",
"capabilities": {"bandwidth":true},
"logToStderr": false,
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"capabilities":{"bandwidth":true},"cniVersion":"1.0.0","name":"test1","type":"cnitesttype"}
]
}]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conflist", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), with options, conflist", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfig := `
{
"cniVersion": "1.0.0",
"name": "test1",
"type": "cnitesttype"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/10-testcni.conflist", multusAutoConfigDir), []byte(masterCNIConfig), 0755)).To(Succeed())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
NamespaceIsolation: true,
GlobalNamespaces: "foobar,barfoo",
MultusLogToStderr: true,
MultusLogLevel: "DEBUG",
MultusLogFile: "/tmp/foobar.log",
AdditionalBinDir: "/tmp/add_bin_dir",
MultusCNIConfDir: "/tmp/multus/net.d",
ReadinessIndicatorFile: "/var/lib/foobar_indicator",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult :=
`{
"cniVersion": "1.0.0",
"name": "multus-cni-network",
"plugins": [ {
"type": "multus",
"namespaceIsolation": true,
"globalNamespaces": "foobar,barfoo",
"logLevel": "debug",
"logFile": "/tmp/foobar.log",
"binDir": "/tmp/add_bin_dir",
"cniConf": "/tmp/multus/net.d",
"readinessindicatorfile": "/var/lib/foobar_indicator",
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"cniVersion":"1.0.0","name":"test1","type":"cnitesttype"}
]
}]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conflist", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createMultusConfig(), with options, conflist", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
multusAutoConfigDir := fmt.Sprintf("%s/auto_conf", tmpDir)
cniConfDir := fmt.Sprintf("%s/cni_conf", tmpDir)
Expect(os.Mkdir(multusAutoConfigDir, 0755)).To(Succeed())
Expect(os.Mkdir(cniConfDir, 0755)).To(Succeed())
// create master CNI config
masterCNIConfigFileName := "10-testcni.conf"
masterCNIConfig := `
{
"cniVersion": "1.0.0",
"name": "test1",
"type": "cnitesttype"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/%s", multusAutoConfigDir, masterCNIConfigFileName), []byte(masterCNIConfig), 0755)).To(Succeed())
// create another CNI config
anotherCNIConfigFileName := "09-test2cni.conf" // Alphabetically before masterCNIConfigFileName
anotherCNIConfig := `
{
"cniVersion": "1.0.0",
"name": "test2",
"type": "cnitest2type"
}`
Expect(os.WriteFile(fmt.Sprintf("%s/%s", multusAutoConfigDir, anotherCNIConfigFileName), []byte(anotherCNIConfig), 0755)).To(Succeed())
masterConfigPath, masterConfigHash, err := (&Options{
MultusAutoconfigDir: multusAutoConfigDir,
MultusMasterCNIFileName: masterCNIConfigFileName,
CNIConfDir: cniConfDir,
MultusKubeConfigFileHost: "/etc/foobar_kubeconfig",
}).createMultusConfig(nil)
Expect(err).NotTo(HaveOccurred())
Expect(masterConfigPath).NotTo(Equal(""))
Expect(masterConfigHash).NotTo(Equal(""))
expectedResult :=
`{
"cniVersion": "1.0.0",
"name": "multus-cni-network",
"plugins": [ {
"type": "multus",
"logToStderr": false,
"kubeconfig": "/etc/foobar_kubeconfig",
"delegates": [
{"cniVersion":"1.0.0","name":"test1","type":"cnitesttype"}
]
}]
}
`
conf, err := os.ReadFile(fmt.Sprintf("%s/00-multus.conflist", cniConfDir))
Expect(string(conf)).To(Equal(expectedResult))
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
It("Run createKubeConfig()", func() {
// create temp dir and files
tmpDir := GinkgoT().TempDir()
cniConfDir := "/cni_conf"
Expect(os.Mkdir(filepath.Join(tmpDir, cniConfDir), 0755)).To(Succeed())
multusConfDir := "/multus_conf"
Expect(os.Mkdir(filepath.Join(tmpDir, multusConfDir), 0755)).To(Succeed())
// Create service account CA file and token file with dummy data
svcAccountPath := filepath.Join(tmpDir, "var/run/secrets/kubernetes.io/serviceaccount")
Expect(os.MkdirAll(svcAccountPath, 0755)).ToNot(HaveOccurred())
svcAccountCAFile := filepath.Join(tmpDir, serviceAccountCAFile)
svcAccountTokenFile := filepath.Join(tmpDir, serviceAccountTokenFile)
Expect(os.WriteFile(svcAccountCAFile, []byte("dummy-ca-content"), 0644)).To(Succeed())
Expect(os.WriteFile(svcAccountTokenFile, []byte("dummy-token-content"), 0644)).To(Succeed())
// Set up the Options struct
options := &Options{
CNIConfDir: cniConfDir,
MultusCNIConfDir: multusConfDir,
}
// Run the createKubeConfig function in a chroot env
back, err := chrootTestHelper(tmpDir)
Expect(err).ToNot(HaveOccurred())
caHash, saTokenHash, err := options.createKubeConfig(nil, nil)
Expect(back()).ToNot(HaveOccurred())
// back to original root
Expect(err).NotTo(HaveOccurred())
Expect(caHash).NotTo(BeNil())
Expect(saTokenHash).NotTo(BeNil())
// Verify the kubeconfig file was created successfully
kubeConfigPath := filepath.Join(tmpDir, cniConfDir, "multus.d", "multus.kubeconfig")
content, err := os.ReadFile(kubeConfigPath)
Expect(err).NotTo(HaveOccurred())
Expect(content).NotTo(BeEmpty())
// Cleanup
Expect(os.RemoveAll(tmpDir)).To(Succeed())
})
})

View File

@@ -1,16 +0,0 @@
# This Dockerfile is used to build the image available on DockerHub
FROM golang:1.17.1 as build
# Add everything
ADD . /usr/src/multus-cni
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
FROM centos:centos7
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni /usr/src/multus-cni
WORKDIR /
ADD ./images/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,21 +0,0 @@
# This Dockerfile is used to build the image available on DockerHub
FROM golang:1.17.1 as build
# Add everything
ADD . /usr/src/multus-cni
ENV GOARCH "arm"
ENV GOOS "linux"
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
# build arm container
FROM arm32v7/centos:7
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni /usr/src/multus-cni
WORKDIR /
ADD ./images/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,21 +0,0 @@
# This Dockerfile is used to build the image available on DockerHub
FROM golang:1.17.1 as build
# Add everything
ADD . /usr/src/multus-cni
ENV GOARCH "arm64"
ENV GOOS "linux"
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
# build arm64 container
FROM arm64v8/centos:7
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni /usr/src/multus-cni
WORKDIR /
ADD ./images/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,21 +0,0 @@
# This Dockerfile is used to build the image available on DockerHub
FROM golang:1.17.1 as build
# Add everything
ADD . /usr/src/multus-cni
ENV GOARCH "ppc64le"
ENV GOOS "linux"
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
# build ppc container
FROM ppc64le/centos:latest
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni /usr/src/multus-cni
WORKDIR /
ADD ./images/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,20 +0,0 @@
# This Dockerfile is used to build the image available on DockerHub
FROM golang:1.17 as build
# Add everything
ADD . /usr/src/multus-cni
ENV GOARCH "s390x"
ENV GOOS "linux"
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
# build s390x container
FROM s390x/python:3-slim
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni /usr/src/multus-cni
WORKDIR /
ADD ./images/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,3 +1,11 @@
# Note:
# This deployment file is designed for 'quickstart' of multus, easy installation to test it,
# hence this deployment yaml does not care about following things intentionally.
# - various configuration options
# - minor deployment scenario
# - upgrade/update/uninstall scenario
# Multus team understand users deployment scenarios are diverse, hence we do not cover
# comprehensive deployment scenario. We expect that it is covered by each platform deployment.
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
@@ -166,6 +174,8 @@ spec:
tolerations:
- operator: Exists
effect: NoSchedule
- operator: Exists
effect: NoExecute
serviceAccountName: multus
containers:
- name: kube-multus
@@ -176,7 +186,6 @@ spec:
- "--cni-version=0.3.1"
- "--cni-bin-dir=/host/usr/libexec/cni"
- "--multus-conf-file=auto"
- "--restart-crio=true"
resources:
requests:
cpu: "100m"
@@ -188,9 +197,11 @@ spec:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- name: run
mountPath: /run
mountPropagation: HostToContainer
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin

View File

@@ -0,0 +1,249 @@
# Note:
# This deployment file is designed for 'quickstart' of multus, easy installation to test it,
# hence this deployment yaml does not care about following things intentionally.
# - various configuration options
# - minor deployment scenario
# - upgrade/update/uninstall scenario
# Multus team understand users deployment scenarios are diverse, hence we do not cover
# comprehensive deployment scenario. We expect that it is covered by each platform deployment.
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
description: 'NetworkAttachmentDefinition is a CRD schema specified by the Network Plumbing
Working Group to express the intent for attaching pods to one or more logical or physical
networks. More information available at: https://github.com/k8snetworkplumbingwg/multi-net-spec'
type: object
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this represen
tation of an object. Servers should convert recognized schemas to the
latest internal value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: 'NetworkAttachmentDefinition spec defines the desired state of a network attachment'
type: object
properties:
config:
description: 'NetworkAttachmentDefinition config is a JSON-formatted CNI configuration'
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- list
- update
- watch
- apiGroups:
- ""
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-daemon-config
namespace: kube-system
labels:
tier: node
app: multus
data:
daemon-config.json: |
{
"chrootDir": "/hostroot",
"cniVersion": "0.3.1",
"logLevel": "verbose",
"logToStderr": true,
"cniConfigDir": "/host/etc/cni/net.d",
"multusAutoconfigDir": "/host/etc/cni/net.d",
"multusConfigFile": "auto",
"socketDir": "/host/run/multus/"
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-multus-ds
namespace: kube-system
labels:
tier: node
app: multus
name: multus
spec:
selector:
matchLabels:
name: multus
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
name: multus
spec:
hostNetwork: true
hostPID: true
tolerations:
- operator: Exists
effect: NoSchedule
- operator: Exists
effect: NoExecute
serviceAccountName: multus
containers:
- name: kube-multus
image: ghcr.io/k8snetworkplumbingwg/multus-cni:snapshot-thick
command: [ "/usr/src/multus-cni/bin/multus-daemon" ]
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
# multus-daemon expects that cnibin path must be identical between pod and container host.
# e.g. if the cni bin is in '/opt/cni/bin' on the container host side, then it should be mount to '/opt/cni/bin' in multus-daemon,
# not to any other directory, like '/opt/bin' or '/usr/bin'.
- name: cnibin
mountPath: /opt/cni/bin
- name: host-run
mountPath: /host/run
- name: host-var-lib-cni-multus
mountPath: /var/lib/cni/multus
- name: host-var-lib-kubelet
mountPath: /var/lib/kubelet
mountPropagation: HostToContainer
- name: host-run-k8s-cni-cncf-io
mountPath: /run/k8s.cni.cncf.io
- name: host-run-netns
mountPath: /run/netns
mountPropagation: HostToContainer
- name: multus-daemon-config
mountPath: /etc/cni/net.d/multus.d
readOnly: true
- name: hostroot
mountPath: /hostroot
mountPropagation: HostToContainer
env:
- name: MULTUS_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
initContainers:
- name: install-multus-binary
image: ghcr.io/k8snetworkplumbingwg/multus-cni:snapshot-thick
command:
- "cp"
- "/usr/src/multus-cni/bin/multus-shim"
- "/host/opt/cni/bin/multus-shim"
resources:
requests:
cpu: "10m"
memory: "15Mi"
securityContext:
privileged: true
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- name: cnibin
mountPath: /host/opt/cni/bin
mountPropagation: Bidirectional
terminationGracePeriodSeconds: 10
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /opt/cni/bin
- name: hostroot
hostPath:
path: /
- name: multus-daemon-config
configMap:
name: multus-daemon-config
items:
- key: daemon-config.json
path: daemon-config.json
- name: host-run
hostPath:
path: /run
- name: host-var-lib-cni-multus
hostPath:
path: /var/lib/cni/multus
- name: host-var-lib-kubelet
hostPath:
path: /var/lib/kubelet
- name: host-run-k8s-cni-cncf-io
hostPath:
path: /run/k8s.cni.cncf.io
- name: host-run-netns
hostPath:
path: /run/netns/

View File

@@ -1,3 +1,11 @@
# Note:
# This deployment file is designed for 'quickstart' of multus, easy installation to test it,
# hence this deployment yaml does not care about following things intentionally.
# - various configuration options
# - minor deployment scenario
# - upgrade/update/uninstall scenario
# Multus team understand users deployment scenarios are diverse, hence we do not cover
# comprehensive deployment scenario. We expect that it is covered by each platform deployment.
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
@@ -166,14 +174,17 @@ spec:
tolerations:
- operator: Exists
effect: NoSchedule
- operator: Exists
effect: NoExecute
serviceAccountName: multus
containers:
- name: kube-multus
image: ghcr.io/k8snetworkplumbingwg/multus-cni:stable
command: ["/entrypoint.sh"]
image: ghcr.io/k8snetworkplumbingwg/multus-cni:snapshot
command: ["/thin_entrypoint"]
args:
- "--multus-conf-file=auto"
- "--cni-version=0.3.1"
- "--multus-autoconfig-dir=/host/etc/cni/net.d"
- "--cni-conf-dir=/host/etc/cni/net.d"
resources:
requests:
cpu: "100m"
@@ -183,6 +194,7 @@ spec:
memory: "50Mi"
securityContext:
privileged: true
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
@@ -190,6 +202,24 @@ spec:
mountPath: /host/opt/cni/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
initContainers:
- name: install-multus-binary
image: ghcr.io/k8snetworkplumbingwg/multus-cni:snapshot
command: ["/install_multus"]
args:
- "--type"
- "thin"
resources:
requests:
cpu: "10m"
memory: "15Mi"
securityContext:
privileged: true
terminationMessagePolicy: FallbackToLogsOnError
volumeMounts:
- name: cnibin
mountPath: /host/opt/cni/bin
mountPropagation: Bidirectional
terminationGracePeriodSeconds: 10
volumes:
- name: cni

View File

@@ -1,7 +1,16 @@
## Multus-cni Configuration Reference
# Multus-cni Configuration Reference
## Introduction
Aside from setting options for Multus, one of the goals of configuration is to set the configuration for your *default network*. The default network is also sometimes referred as the "primary CNI plugin", the "primary network", or a "default CNI plugin" and is the CNI plugin that is used to implement [the Kubernetes networking model](https://kubernetes.io/docs/concepts/services-networking/#the-kubernetes-network-model) in your cluster. Common examples include Flannel, Weave, Calico, Cillium, and OVN-Kubernetes, among others.
Here we will refer to this as your default CNI plugin or default network.
## Example configuration
Following is the example of multus config file, in `/etc/cni/net.d/`.
(`"Note1"` and `"Note2"` are just comments, so you can remove them at your configuration)
Example configuration using `clusterNetwork` (see also [using delegates](#using-delegates))
```
{
@@ -14,17 +23,99 @@ Following is the example of multus config file, in `/etc/cni/net.d/`.
"binDir": "/opt/cni/bin",
"logFile": "/var/log/multus.log",
"logLevel": "debug",
"logOptions": {
"maxAge": 5,
"maxSize": 100,
"maxBackups": 5,
"compress": true
},
"capabilities": {
"portMappings": true
},
"readinessindicatorfile": "",
"namespaceIsolation": false,
"Note1":"NOTE: you can set clusterNetwork+defaultNetworks OR delegates!!",
"clusterNetwork": "defaultCRD",
"defaultNetworks": ["sidecarCRD", "flannel"],
"clusterNetwork": "/etc/cni/net.d/99-flannel.conf",
"defaultNetworks": ["sidecarCRD", "exampleNetwork"],
"systemNamespaces": ["kube-system", "admin"],
"multusNamespace": "kube-system",
"Note2":"NOTE: If you use clusterNetwork/defaultNetworks, delegates is ignored",
allowTryDeleteOnErr: false
}
```
## Index of configuration options
This is a general index of options, however note that you must set either the `clusterNetwork` or `delegates` options, see the following sections after the index for details.
* `name` (string, required): The name of the network
* `type` (string, required): Must be set to the value of &quot;multus&quot;
* `confDir` (string, optional): directory for CNI config file that multus reads. default `/etc/cni/multus/net.d`
* `cniDir` (string, optional): Multus CNI data directory, default `/var/lib/cni/multus`
* `binDir` (string, optional): additional directory for CNI plugins which multus calls, in addition to the default (the default is typically set to `/opt/cni/bin`)
* `kubeconfig` (string, optional): kubeconfig file for the out of cluster communication with kube-apiserver. See the example [kubeconfig](https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/node-kubeconfig.yaml). If you would like to use CRD (i.e. network attachment definition), this is required
* [`logToStderr`](#Logging-via-STDERR) (bool, optional): Enable or disable logging to `STDERR`. Defaults to true.
* [`logFile`](#Writing-to-a-Log-File) (string, optional): file path for log file. multus puts log in given file
* [`logLevel`](#Logging-Level) (string, optional): logging level (values in decreasing order of verbosity: "debug", "error", "verbose", or "panic")
* [`logOptions`](#Logging-Options) (object, optional): logging option, More detailed log configuration
* [`namespaceIsolation`](#Namespace-Isolation) (boolean, optional): Enables a security feature where pods are only allowed to access `NetworkAttachmentDefinitions` in the namespace where the pod resides. Defaults to false.
* [`globalNamespaces`](#Allow-specific-namespaces-to-be-used-across-namespaces-when-using-namespace-isolation): (string, optional): Used only when `namespaceIsolation` is true, allows specification of comma-delimited list of namespaces which may be referred to outside of namespace isolation.
* `capabilities` ({}list, optional): [capabilities](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#dynamic-plugin-specific-fields-capabilities--runtime-configuration) supported by at least one of the delegates. (NOTE: Multus only supports portMappings/Bandwidth capability for cluster networks).
* [`readinessindicatorfile`](#Default-Network-Readiness-Indicator): The path to a file whose existence denotes that the default network is ready
message to next when some missing error. Defaults to false.
* `systemNamespaces` ([]string, optional): list of namespaces for Kubernetes system (namespaces listed here will not have `defaultNetworks` added)
* `multusNamespace` (string, optional): namespace for `clusterNetwork`/`defaultNetworks` (the default value is `kube-system`)
* `retryDeleteOnError` (bool, optional): Enable or disable delegate DEL
### Using `clusterNetwork`
Using the `clusterNetwork` option and the `delegates` are **mutually exclusive**. If `clusterNetwork` is set, the `delegates` field is *ignored*.
You **must** set one or the other.
Therefore:
* Set `clusterNetwork` and if this is set, optionally set the `defaultNetworks`.
* OR you **must** set `delegates`.
Options:
* `clusterNetwork` (string, required if not using `delegates`): the default CNI plugin to be executed.
* `defaultNetworks` ([]string, optional): Additional / secondary network attachment that is always attached to each pod.
The following values are valid for both `clusterNetwork` and `defaultNetworks` and are processed in the following order:
* The name of a `NetworkAttachmentDefinition` custom resource in the namespace specified by the `multusNamespace` configuration option
* The `"name"` value in the contents of a CNI JSON configuration file in the CNI configuration directory,
* The given name for `clusterNetwork` should match the value for `name` key in the contents of the CNI JSON file (e.g. `"name": "test"` in `my.conf` when `"clusterNetwork": "test"`)
* A path to a directory containing CNI json configuration files. The alphabetically first file will be used.
* Absolute file path for CNI config file
* If none of the above are found using the value, Multus will raise an error.
If for example you have `defaultNetworks` set as:
```
"defaultNetworks": ["sidecarNetwork", "exampleNetwork"],
```
In this example, the values in the expression refer to `NetworkAttachmentDefinition` custom resource names. Therefore, there must be `NetworkAttachmentDefinitions` already created with the names `sidecarNetwork` and `exampleNetwork`.
This means that in addition to the cluster network, each pod would be assigned two additional networks by default, and the pod would present three interfaces, e.g. `eth0`, `net1`, and `net2`, with `net1` and `net2` being set by the above described `NetworkAttachmentDefinitions`. Additional attachments as made by setting `k8s.v1.cni.cncf.io/networks` on pods will be made in addition to those set in the `defaultNetworks` configuration option.
### Using `delegates`
If `clusterNetwork` is not set, you **must** use `delegates`.
* `delegates` ([]map, required if not using `clusterNetwork`). List of CNI configurations to be used as your default CNI plugin(s).
Example configuration using `delegates`:
```
{
"cniVersion": "0.3.1",
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"confDir": "/etc/cni/multus/net.d",
"cniDir": "/var/lib/cni/multus",
"binDir": "/opt/cni/bin",
"delegates": [{
"type": "weave-net",
"hairpinMode": true
@@ -35,43 +126,13 @@ Following is the example of multus config file, in `/etc/cni/net.d/`.
}
```
* `name` (string, required): the name of the network
* `type` (string, required): &quot;multus&quot;
* `confDir` (string, optional): directory for CNI config file that multus reads. default `/etc/cni/multus/net.d`
* `cniDir` (string, optional): Multus CNI data directory, default `/var/lib/cni/multus`
* `binDir` (string, optional): additional directory for CNI plugins which multus calls, in addition to the default (the default is typically set to `/opt/cni/bin`)
* `kubeconfig` (string, optional): kubeconfig file for the out of cluster communication with kube-apiserver. See the example [kubeconfig](https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/node-kubeconfig.yaml). If you would like to use CRD (i.e. network attachment definition), this is required
* `logToStderr` (bool, optional): Enable or disable logging to `STDERR`. Defaults to true.
* `logFile` (string, optional): file path for log file. multus puts log in given file
* `logLevel` (string, optional): logging level ("debug", "error", "verbose", or "panic")
* `namespaceIsolation` (boolean, optional): Enables a security feature where pods are only allowed to access `NetworkAttachmentDefinitions` in the namespace where the pod resides. Defaults to false.
* `capabilities` ({}list, optional): [capabilities](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#dynamic-plugin-specific-fields-capabilities--runtime-configuration) supported by at least one of the delegates. (NOTE: Multus only supports portMappings/Bandwidth capability for cluster networks).
* `readinessindicatorfile`: The path to a file whose existence denotes that the default network is ready
User should chose following parameters combination (`clusterNetwork`+`defaultNetworks` or `delegates`):
* `clusterNetwork` (string, required): default CNI network for pods, used in kubernetes cluster (Pod IP and so on): name of network-attachment-definition, CNI json file name (without extension, .conf/.conflist) or directory for CNI config file
* `defaultNetworks` ([]string, required): default CNI network attachment: name of network-attachment-definition, CNI json file name (without extension, .conf/.conflist) or directory for CNI config file
* `systemNamespaces` ([]string, optional): list of namespaces for Kubernetes system (namespaces listed here will not have `defaultNetworks` added)
* `multusNamespace` (string, optional): namespace for `clusterNetwork`/`defaultNetworks`
* `delegates` ([]map,required): number of delegate details in the Multus
### Network selection flow of clusterNetwork/defaultNetworks
Multus will find network for clusterNetwork/defaultNetworks as following sequences:
1. CRD object for given network name, in 'kube-system' namespace
1. CNI json config file in `confDir`. Given name should be without extension, like .conf/.conflist. (e.g. "test" for "test.conf"). The given name for `clusterNetwork` should match the value for `name` key in the config file (e.g. `"name": "test"` in "test.conf" when `"clusterNetwork": "test"`)
1. Directory for CNI json config file. Multus will find alphabetically first file for the network
1. Multus failed to find network. Multus raise error message
## Miscellaneous config
## Configuration Option Details
### Default Network Readiness Indicator
You may wish for your "default network" (that is, the CNI plugin & its configuration you specify as your default delegate) to become ready before you attach networks with Multus. This is disabled by default and not used unless you add the readiness check option(s) to your CNI configuration file.
You may desire that your default network becomes ready before attaching networks with Multus. This is disabled by default and not used unless you set the `readinessindicatorfile` option to a non-blank value.
For example, if you use Flannel as a default network, the recommended method for Flannel to be installed is via a daemonset that also drops a configuration file in `/etc/cni/net.d/`. This may apply to other plugins that place that configuration file upon their readiness, hence, Multus uses their configuration filename as a semaphore and optionally waits to attach networks to pods until that file exists.
For example, if you use Flannel as a default network, the recommended method for Flannel to be installed is via a daemonset that also drops a configuration file in `/etc/cni/net.d/`. This may apply to other plugins that place that configuration file upon their readiness, therefore, Multus uses their configuration filename as a semaphore and optionally waits to attach networks to pods until that file exists.
In this manner, you may prevent pods from crash looping, and instead wait for that default network to be ready.
@@ -103,7 +164,7 @@ Optionally, you may have Multus log to a file on the filesystem. This file will
For example in your CNI configuration, you may set:
```
"LogFile": "/var/log/multus.log",
"logFile": "/var/log/multus.log",
```
#### Logging Level
@@ -120,7 +181,27 @@ The available logging level values, in decreasing order of verbosity are:
You may configure the logging level by using the `LogLevel` option in your CNI configuration. For example:
```
"LogLevel": "debug",
"logLevel": "debug",
```
#### Logging Options
If you want a more detailed configuration of the logging, This includes the following parameters:
* `maxAge` the maximum number of days to retain old log files in their filename
* `maxSize` the maximum size in megabytes of the log file before it gets rotated
* `maxBackups` the maximum number of days to retain old log files in their filename
* `compress` compress determines if the rotated log files should be compressed using gzip
For example in your CNI configuration, you may set:
```
"logOptions": {
"maxAge": 5,
"maxSize": 100,
"maxBackups": 5,
"compress": true
}
```
### Namespace Isolation

View File

@@ -1,4 +1,14 @@
## Development Information
## Development/Support Information
## Which Kubernetes version is supported in multus?
Currently multus team supports Kubernetes that Kubernetes community maintains.
See [Version Skew Policy](https://kubernetes.io/releases/version-skew-policy/) for the details.
## How to debug multus-cni thin image?
Latest multus uses [distroless](https://github.com/GoogleContainerTools/distroless) container image for its base,
hence there is no shell command. If you want to execute shell in multus pod, please use `-debug` image (e.g. ghcr.io/k8snetworkplumbingwg/multus-cni:snapshot-debug), which has shell.
## How to utilize multus-cni code as library?
@@ -6,10 +16,9 @@ Multus now uses [gopkg.in](http://gopkg.in/) to expose its code as library.
You can use following command to import our code into your go code.
```
go get gopkg.in/k8snetworkplumbingwg/multus-cni.v3
go get gopkg.in/k8snetworkplumbingwg/multus-cni.v4
```
## How do I submit an issue?
Use GitHub as normally, you'll be presented with an option to submit a issue or enhancement request.
@@ -35,7 +44,7 @@ cd multus-cni
Multus has go unit tests (based on ginkgo framework).The following commands drive CI tests manually in your environment:
```
sudo ./scripts/test.sh
sudo ./hack/test-go.sh
```
## What are the best practices for logging?
@@ -50,3 +59,7 @@ The following are the best practices for multus logging:
## Multus release schedule
On the first maintainer's meeting, twice yearly, after January 1st and July 1st, if a new version has not been tagged, a new version will tagged.
## Multi-arch builds
Multus is currently built for a number of architectures, however, our testing and validation is only performed against x86 architectures. Our x86 architecture has end to end testing, however, for other architectures, only supported via best effort community contributions.

View File

@@ -15,13 +15,17 @@ Generally we recommend two options: Manually place a Multus binary in your `/opt
You may acquire the Multus binary via compilation (see the [developer guide](development.md)) or download the a binary from the [GitHub releases](https://github.com/k8snetworkplumbingwg/multus-cni/releases) page. Copy multus binary into CNI binary directory, usually `/opt/cni/bin`. Perform this on all nodes in your cluster (master and nodes).
$ cp multus /opt/cni/bin
cp multus /opt/cni/bin
*Via Daemonset method*
As a [quickstart](quickstart.md), you may apply these YAML files (included in the clone of this repository). Run this command (typically you would run this on the master, or wherever you have access to the `kubectl` command to manage your cluster).
As a [quickstart](quickstart.md), you may apply these YAML files. Run this command (typically you would run this on the master, or wherever you have access to the `kubectl` command to manage your cluster).
$ cat ./images/{multus-daemonset.yml,flannel-daemonset.yml} | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml # thin deployment
or
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml # thick (client/server) deployment
If you need more comprehensive detail, continue along with this guide, otherwise, you may wish to either [follow the quickstart guide]() or skip to the ['Create network attachment definition'](#create-network-attachment-definition) section.
@@ -34,12 +38,12 @@ You put CNI config file in `/etc/cni/net.d`. Kubernetes CNI runtime uses the alp
Execute following commands at all Kubernetes nodes (i.e. master and minions)
```
$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/00-multus.conf <<EOF
mkdir -p /etc/cni/net.d
cat >/etc/cni/net.d/00-multus.conf <<EOF
{
"name": "multus-cni-network",
"type": "multus",
"readinessindicatorfile": "/var/run/flannel/subnet.env",
"readinessindicatorfile": "/run/flannel/subnet.env",
"delegates": [
{
"NOTE1": "This is example, wrote your CNI config in delegates",
@@ -72,7 +76,7 @@ Create resources for multus to access CRD objects as following command:
```
# Execute following commands at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ServiceAccount
metadata:
@@ -119,13 +123,13 @@ Create kubeconfig at master node as following commands:
```
# Execute following command at Kubernetes master
$ mkdir -p /etc/cni/net.d/multus.d
$ SERVICEACCOUNT_CA=$(kubectl get secrets -n=kube-system -o json | jq -r '.items[]|select(.metadata.annotations."kubernetes.io/service-account.name"=="multus")| .data."ca.crt"')
$ SERVICEACCOUNT_TOKEN=$(kubectl get secrets -n=kube-system -o json | jq -r '.items[]|select(.metadata.annotations."kubernetes.io/service-account.name"=="multus")| .data.token' | base64 -d )
$ KUBERNETES_SERVICE_PROTO=$(kubectl get all -o json | jq -r .items[0].spec.ports[0].name)
$ KUBERNETES_SERVICE_HOST=$(kubectl get all -o json | jq -r .items[0].spec.clusterIP)
$ KUBERNETES_SERVICE_PORT=$(kubectl get all -o json | jq -r .items[0].spec.ports[0].port)
$ cat > /etc/cni/net.d/multus.d/multus.kubeconfig <<EOF
mkdir -p /etc/cni/net.d/multus.d
SERVICEACCOUNT_CA=$(kubectl get secrets -n=kube-system -o json | jq -r '.items[]|select(.metadata.annotations."kubernetes.io/service-account.name"=="multus")| .data."ca.crt"')
SERVICEACCOUNT_TOKEN=$(kubectl get secrets -n=kube-system -o json | jq -r '.items[]|select(.metadata.annotations."kubernetes.io/service-account.name"=="multus")| .data.token' | base64 -d )
KUBERNETES_SERVICE_PROTOCOL=$(kubectl get all -o json | jq -r .items[0].spec.ports[0].name)
KUBERNETES_SERVICE_HOST=$(kubectl get all -o json | jq -r .items[0].spec.clusterIP)
KUBERNETES_SERVICE_PORT=$(kubectl get all -o json | jq -r .items[0].spec.ports[0].port)
cat > /etc/cni/net.d/multus.d/multus.kubeconfig <<EOF
# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
@@ -151,7 +155,7 @@ Copy `/etc/cni/net.d/multus.d/multus.kubeconfig` into other Kubernetes nodes
**NOTE: Recommend to exec 'chmod 600 /etc/cni/net.d/multus.d/multus.kubeconfig' to keep secure**
```
$ scp /etc/cni/net.d/multus.d/multus.kubeconfig ...
scp /etc/cni/net.d/multus.d/multus.kubeconfig ...
```
### Setup CRDs (daemonset automatically does)
@@ -162,7 +166,7 @@ Create CRD definition in Kubernetes as following command at master node:
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
@@ -200,7 +204,7 @@ Following command creates NetworkAttachmentDefinition. CNI config is in `config:
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
@@ -232,7 +236,7 @@ If NetworkAttachmentDefinition has no spec, multus find a file in defaultConfDir
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
@@ -242,7 +246,7 @@ EOF
```
# Execute following commands at all Kubernetes nodes (i.e. master and minions)
$ cat <<EOF > /etc/cni/multus/net.d/macvlan2.conf
cat <<EOF > /etc/cni/multus/net.d/macvlan2.conf
{
"cniVersion": "0.3.0",
"type": "macvlan",
@@ -260,6 +264,7 @@ $ cat <<EOF > /etc/cni/multus/net.d/macvlan2.conf
]
}
}
EOF
```
### Run pod with network annotation
@@ -268,7 +273,7 @@ $ cat <<EOF > /etc/cni/multus/net.d/macvlan2.conf
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
@@ -290,7 +295,7 @@ You can also specify NetworkAttachmentDefinition with its namespace as adding `<
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
@@ -314,7 +319,7 @@ spec:
}
}'
EOF
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
@@ -336,7 +341,7 @@ You can also specify interface name as adding `@<ifname>`.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
@@ -356,7 +361,7 @@ EOF
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
@@ -381,7 +386,7 @@ You can also specify NetworkAttachmentDefinition with its namespace as adding `"
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
@@ -406,7 +411,7 @@ You can also specify interface name as adding `"interface": "<ifname>"`.
```
# Execute following command at Kubernetes master
$ cat <<EOF | kubectl create -f -
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
@@ -432,7 +437,8 @@ Following the example of `ip -d address` output of above pod, "pod-case-06":
```
# Execute following command at Kubernetes master
$ kubectl exec -it pod-case-06 -- ip -d address
kubectl exec -it pod-case-06 -- ip -d address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 127.0.0.1/8 scope host lo
@@ -505,7 +511,7 @@ spec:
EOF
```
We can then create a pod which uses the `default-route` key in the JSON formatted `k8s.v1.cni.cncf.io/networks` annotation.
We can then create a pod which uses the `default-route` key in the JSON formatted `k8s.v1.cni.cncf.io/networks` annotation.
```
cat <<EOF | kubectl create -f -
@@ -529,10 +535,11 @@ EOF
This will set `192.168.2.1` as the default route over the `net1` interface, such as:
```
$ kubectl exec -it samplepod -- ip route
default via 192.168.2.1 dev net1
10.244.0.0/24 dev eth0 proto kernel scope link src 10.244.0.169
10.244.0.0/16 via 10.244.0.1 dev eth0
kubectl exec -it samplepod -- ip route
default via 192.168.2.1 dev net1
10.244.0.0/24 dev eth0 proto kernel scope link src 10.244.0.169
10.244.0.0/16 via 10.244.0.1 dev eth0
```
## Entrypoint Parameters
@@ -544,7 +551,7 @@ Typically, you'd modified the daemonset YAML itself to specify these parameters.
For example, the `command` and `args` parameters in the `containers` section of the DaemonSet may look something like:
```
command: ["/entrypoint.sh"]
command: ["/thin_entrypoint"]
args:
- "--multus-conf-file=auto"
- "--namespace-isolation=true"
@@ -583,7 +590,7 @@ The `--multus-conf-file` is one of two options; it can be set to a source file t
The automatic configuration option is used to automatically generate Multus configurations given existing on-disk CNI configurations for your default network.
In the case that `--multus-conf-file=auto` -- The entrypoint script will look at the `--multus-autoconfig-dir` (by default, the same as the `--cni-conf-dir`). Multus will wait (600 seconds) until there's a CNI configuration file there, and it will take the alphabetically first configuration there, and it will wrap that configuration into a Multus configuration.
In the case that `--multus-conf-file=auto` -- The entrypoint script will look at the `--multus-autoconfig-dir` (by default, the same as the `--cni-conf-dir`). Multus will take the alphabetically first configuration there and wrap that into a Multus configuration.
--multus-autoconfig-dir=/host/etc/cni/net.d
@@ -602,7 +609,7 @@ The `--multus-master-cni-file-name` can be used to select the cni file as the ma
--multus-log-level=
--multus-log-file=
Used only with `--multus-conf-file=auto`. See the documentation for logging for which values are permitted.
Used only with `--multus-conf-file=auto`. See the [documentation for logging](https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/configuration.md#logging) for which values are permitted.
Used only with `--multus-conf-file=auto`. Allows you to specify CNI spec version. Please set if you need to specify CNI spec version.
@@ -612,9 +619,13 @@ In some cases, the original CNI configuration that the Multus configuration was
--cleanup-config-on-exit=true
When using CRIO, you may need to restart CRIO to get the Multus configuration file to take -- this is rarely necessary.
When specifying `--cleanup-config-on-exit=true` the entrypoint script will delete any generated/copied Multus configuration files when entrypoint script
exits (upon Pod termination). This allows Multus to be safely removed from the cluster when its no longer needed.
--restart-crio=false
In addition, when both `--cleanup-config-on-exit=true` and `--multus-conf-file=auto` are specified, the entrypoint script will watch for changes of the
master CNI configuration and kubeconfig. when such change detected, the script will re-genereate Multus configuration. Watch can be skipped by setting:
--skip-config-watch
Additionally when using CRIO, you may wish to have the CNI config file that's used as the source for `--multus-conf-file=auto` renamed. This boolean option when set to true automatically renames the file with a `.old` suffix to the original filename.
@@ -631,3 +642,126 @@ Sometimes, you may wish to not have the entrypoint copy the binary file onto the
If you wish to have auto configuration use the `readinessindicatorfile` in the configuration, you can use the `--readiness-indicator-file` to express which file should be used as the readiness indicator.
--readiness-indicator-file=/path/to/file
### Run pod with network annotation and Dynamic Resource Allocation driver
> :warning: Dynamic Resource Allocation (DRA) is [currently an alpha](https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/),
> and is subject to change. Please consider this functionality as a preview. The architecture and usage of DRA in
> Multus CNI may change in the future as this technology matures.
>
> The current DRA integration is based on the DRA API for Kubernetes 1.26 to 1.30. With Kubernetes 1.31, the DRA API
> will change and multus doesn't integrate with the new API yet.
Dynamic Resource Allocation is alternative mechanism to device plugin which allows to requests pod and container
resources.
The following sections describe how to use DRA with multus and NVIDIA DRA driver. Other DRA networking driver vendors
should follow similar concepts to make use of multus DRA support.
#### Prerequisite
1. Kubernetes 1.27
2. Container Runtime with CDI support enabled
3. Kubernetes runtime-config=resource.k8s.io/v1alpha2
4. Kubernetes feature-gates=DynamicResourceAllocation=True,KubeletPodResourcesDynamicResources=true
#### Install DRA driver
The current example uses NVIDIA DRA driver for networking. This DRA driver is not publicly available. An alternative to
this DRA driver is available at [dra-example-driver](https://github.com/kubernetes-sigs/dra-example-driver).
#### Create dynamic resource class with NVIDIA network DRA driver
The `ResourceClass` defines the resource pool of `sf-pool-1`.
```
# Execute following command at Kubernetes master
cat <<EOF | kubectl create -f -
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClass
metadata:
name: sf-pool-1
driverName: net.resource.nvidia.com
EOF
```
#### Create network attachment definition with resource name
The `k8s.v1.cni.cncf.io/resourceName` should match the `ResourceClass` name defined in the section above.
In this example it is `sf-pool-1`. Multus query the K8s PodResource API to fetch the `resourceClass` name and also
query the NetworkAttachmentDefinition `k8s.v1.cni.cncf.io/resourceName`. If both has the same name multus send the
CDI device name in the DeviceID argument.
##### NetworkAttachmentDefinition for ovn-kubernetes example:
Following command creates NetworkAttachmentDefinition. CNI config is in `config:` field.
```
# Execute following command at Kubernetes master
cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: default
annotations:
k8s.v1.cni.cncf.io/resourceName: sf-pool-1
spec:
config: '{
"cniVersion": "0.4.0",
"dns": {},
"ipam": {},
"logFile": "/var/log/ovn-kubernetes/ovn-k8s-cni-overlay.log",
"logLevel": "4",
"logfile-maxage": 5,
"logfile-maxbackups": 5,
"logfile-maxsize": 100,
"name": "ovn-kubernetes",
"type": "ovn-k8s-cni-overlay"
}'
EOF
```
#### Create DRA Resource Claim
Following command creates `ResourceClaim` `sf` which request resource from `ResourceClass` `sf-pool-1`.
```
# Execute following command at Kubernetes master
cat <<EOF | kubectl create -f -
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaim
metadata:
namespace: default
name: sf
spec:
spec:
resourceClassName: sf-pool-1
EOF
```
#### Launch pod with DRA Resource Claim
Following command Launch a Pod with primiry network `default` and `ResourceClaim` `sf`.
```
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: test-sf-claim
annotations:
v1.multus-cni.io/default-network: default
spec:
restartPolicy: Always
containers:
- name: with-resource
image: docker.io/library/ubuntu:22.04
command: ["/bin/sh", "-ec", "while :; do echo '.'; sleep 5 ; done"]
resources:
claims:
- name: resource
resourceClaims:
- name: resource
source:
resourceClaimName: sf
```

View File

@@ -15,7 +15,7 @@ Two things we'll refer to a number of times through this document are:
Our installation method requires that you first have installed Kubernetes and have configured a default network -- that is, a CNI plugin that's used for your pod-to-pod connectivity.
We recommend Kubernetes 1.16 or later.
We support Kubernetes versions that Kubernetes community supports. Please see [Supported versions](https://kubernetes.io/releases/version-skew-policy/#supported-versions) in Kubernetes document.
To install Kubernetes, you may decide to use [kubeadm](https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/), or potentially [kubespray](https://github.com/kubernetes-sigs/kubespray).
@@ -42,22 +42,25 @@ master-2 Ready master 1h v1.17.1
Our recommended quickstart method to deploy Multus is to deploy using a Daemonset (a method of running pods on each nodes in your cluster), this spins up pods which install a Multus binary and configure Multus for usage.
Firstly, clone this GitHub repository.
We'll apply a YAML file with `kubectl` from this repo, which installs the Multus components.
Recommended installation:
```
git clone https://github.com/k8snetworkplumbingwg/multus-cni.git && cd multus-cni
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml
```
See the [thick plugin docs](./thick-plugin.md) for more information about this architecture.
We'll apply a YAML file with `kubectl` from this repo.
Alternatively, you may install the thin-plugin with:
```
$ cat ./images/multus-daemonset.yml | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml
```
### What the Multus daemonset does
* Starts a Multus daemonset, this runs a pod on each node which places a Multus binary on each node in `/opt/cni/bin`
* Reads the lexicographically (alphabetically) first configuration file in `/etc/cni/net.d`, and creates a new configuration file for Multus as `/etc/cni/net.d/00-multus.conf`, this configuration is auto-generated and is based on the default network configuration (which is assumed to be the alphabetically first configuration)
* Reads the lexicographically (alphabetically) first configuration file in `/etc/cni/net.d`, and creates a new configuration file for Multus on each node as `/etc/cni/net.d/00-multus.conf`, this configuration is auto-generated and is based on the default network configuration (which is assumed to be the alphabetically first configuration)
* Creates a `/etc/cni/net.d/multus.d` directory on each node with authentication information for Multus to access the Kubernetes API.
@@ -66,7 +69,7 @@ $ cat ./images/multus-daemonset.yml | kubectl apply -f -
Generally, the first step in validating your installation is to ensure that the Multus pods have run without error, you may see an overview of those by looking at:
```
$ kubectl get pods --all-namespaces | grep -i multus
kubectl get pods --all-namespaces | grep -i multus
```
You may further validate that it has ran by looking at the `/etc/cni/net.d/` directory and ensure that the auto-generated `/etc/cni/net.d/00-multus.conf` exists corresponding to the alphabetically first configuration file.
@@ -176,7 +179,7 @@ EOF
You may now inspect the pod and see what interfaces are attached, like so:
```
$ kubectl exec -it samplepod -- ip a
kubectl exec -it samplepod -- ip a
```
You should note that there are 3 interfaces:

113
docs/thick-plugin.md Normal file
View File

@@ -0,0 +1,113 @@
# Multus Thick plugin
Multus CNI can also be deployed using a thick plugin architecture, which is
characterized by a client/server architecture.
The client - which will be referred to as "shim" - is a binary executable
located on the Kubernetes node's file-system that
[speaks CNI](https://github.com/containernetworking/cni/blob/master/SPEC.md#section-2-execution-protocol):
the runtime - Kubernetes - passes parameters to the plugin via environment
variables and configuration - which is passed via stdin.
The plugin returns a result on stdout on success, or an error on stderr if the
operation fails. Configuration and results are a JSON encoded string.
Once the shim is invoked by the runtime (Kubernetes) it will contact the
multus-daemon (server) via a unix domain socket which is bind mounted to the
host's file-system; the multus-daemon is the one that will do all the
heavy-pulling: fetch the delegate CNI configuration from the corresponding
`net-attach-def`, compute the `RuntimeConfig`, and finally, invoke the delegate.
It will then return the result of the operation back to the client.
Please refer to the diagram below for a visual representation of the flow
described above:
```
┌─────────┐ ┌───────┐ ┌────────┐ ┌──────────┐
│ │ cni ADD/DEL │ │ REST POST │ │ cni ADD/DEL │ │
│ runtime ├────────────►│ shim │===========│ daemon ├────────────►│ delegate │
│ │<------------│ │ │ │<------------│ │
└─────────┘ └───────┘ └────────┘ └──────────┘
```
## How to use it
### Configure Deployment
If your delegate CNI plugin requires some files which is in container host, please update
update `deployments/multus-daemonset-thick.yml` to add directory into multus-daemon pod.
For example, flannel requires `/run/flannel/subnet.env`, so you need to mount this directory
into the multus-daemon pod.
Required directory/files are different for each CNI plugin, so please refer your CNI plugin.
### Deployment
There is a dedicated multus daemonset specification for users wanting to use
this thick plugin variant. This reference deployment spec of multus can be
deployed by following these commands:
```bash
kubectl apply -f deployments/multus-daemonset-thick.yml
```
### Command line parameters
The available command line parameters are:
- `config`: Defaults to `"/etc/cni/net.d/multus.d/daemon-config.json"`
- `version`: Prints the daemon config version and exits
### Server / Daemon configuration
The server configuration is encoded in JSON, and allows the following keys:
- `"chrootDir"`: Specify the directory which points to host root from the pod. See 'Chroot configuration' section for the details.
- `"socketDir"`: Specify the location where the unix domain socket used
for client/server communication will be located. This is the location where the
**Daemon** will read the configuration from. Defaults to `"/run/multus"`.
- `"metricsPort"`: Metrics port (of multus' metric exporter); by default, no port
is provided.
- `"logFile"`: the path to where the daemon logs will be persisted.
- `"logLevel"`: the logging level for the multus daemon logs.
- `"logToStderr"`: enable this to have the daemon multus logs echoed to stderr
as well. By default, it is disabled.
In addition, you can add any configuration which is in [configuration reference](https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/configuration.md#multus-cni-configuration-reference). Server configuration override multus CNI configuration (e.g. `/etc/cni/net.d/00-multus.conf`)
Below you can see an example of the daemon configuration:
```json
{
"chrootDir": "/hostroot",
"confDir": "/host/etc/cni/net.d",
"logToStderr": true,
"logLevel": "verbose",
"logFile": "/tmp/multus.log",
"binDir": "/opt/cni/bin",
"cniDir": "/var/lib/cni/multus",
"socketDir": "/host/run/multus/",
"cniVersion": "0.3.1",
"cniConfigDir": "/host/etc/cni/net.d",
"multusConfigFile": "auto",
"multusAutoconfigDir": "/host/etc/cni/net.d"
}
```
### Client / Shim configuration
The multus shim configuration is encoded in JSON, and essentially is just a
regular CNI configuration, usually available in `/etc/cni/net.d/00-multus.conf`.
It allows the following keys:
- `"cniVersion"`: the CNI version for the Multus CNI plugin.
- `"logFile"`: the path to where the daemon logs will be persisted.
- `"logLevel"`: the logging level for the multus daemon logs.
- `"logToStderr"`: enable this to have the daemon multus logs echoed to stderr
as well. By default, it is disabled.
#### Chroot configuration
In thick plugin case, delegate CNI plugin is executed by multus-daemon from Pod, hence if the delegate CNI requires resources in container host, for example unix socket or even file, then CNI plugin is failed to execute because multus-daemon runs in Pod. Multus-daemon supports "chrootDir" option which executes delegate CNI under chroot (to container host).
This configuration is enabled in deployments/multus-daemonset-thick.yml as default.

View File

@@ -1,12 +1,20 @@
## Multus e2e test with kind
### How to test e2e
### Prerequisite
To run the e2e test, you need the following components:
- curl
- jinjanator
- docker
### How to test e2e
```
$ git clone https://github.com/k8snetworkplumbingwg/multus-cni.git
$ cd multus-cni/e2e
$ ./get_tools.sh
$ ./generate_yamls.sh
$ ./setup_cluster.sh
$ ./test-simple-macvlan1.sh
```

17
e2e/generate_yamls.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
if [ ! -d yamls ]; then
mkdir yamls
fi
# specify CNI version (default: 0.4.0)
export CNI_VERSION=${CNI_VERSION:-0.4.0}
templates_dir="$(dirname $(readlink -f $0))/templates"
# generate yaml files based on templates/*.j2 to yamls directory
for i in `ls templates/`; do
echo $i
j2 -e CNI_VERSION ${templates_dir}/$i -o yamls/${i%.j2}
done
unset CNI_VERSION

View File

@@ -5,11 +5,12 @@ if [ ! -d bin ]; then
mkdir bin
fi
curl -Lo ./bin/kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.8.1/kind-$(uname)-amd64"
curl -Lo ./bin/kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.22.0/kind-$(uname)-amd64"
chmod +x ./bin/kind
curl -Lo ./bin/kubectl https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./bin/kubectl
curl -Lo ./bin/koko https://github.com/redhat-nfvpe/koko/releases/download/v0.82/koko_0.82_linux_amd64
curl -Lo ./bin/koko https://github.com/redhat-nfvpe/koko/releases/download/v0.83/koko_0.83_linux_amd64
chmod +x ./bin/koko
curl -Lo ./bin/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
chmod +x ./bin/jq
wget -qO- https://get.helm.sh/helm-v3.14.3-linux-amd64.tar.gz | tar xvzf - --strip-components=1 -C ./bin linux-amd64/helm

View File

@@ -3,66 +3,67 @@ set -o errexit
export PATH=${PATH}:./bin
# define the OCI binary to be used. Acceptable values are `docker`, `podman`.
# Defaults to `docker`.
OCI_BIN="${OCI_BIN:-docker}"
# define the deployment spec to use when deploying multus.
# Acceptable values are `multus-daemonset.yml`. `multus-daemonset-thick.yml`.
# Defaults to `multus-daemonset-thick.yml`.
MULTUS_MANIFEST="${MULTUS_MANIFEST:-multus-daemonset-thick.yml}"
# define the dockerfile to build multus.
# Acceptable values are `Dockerfile`. `Dockerfile.thick`.
# Defaults to `Dockerfile.thick`.
MULTUS_DOCKERFILE="${MULTUS_DOCKERFILE:-Dockerfile.thick}"
kind_network='kind'
reg_name='kind-registry'
reg_port='5000'
running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)"
if [ "${running}" != 'true' ]; then
# run registry and push the multus image
docker run -d --restart=always -p "${reg_port}:5000" --name "${reg_name}" registry:2
docker build -t localhost:5000/multus:e2e -f ../deployments/Dockerfile ..
docker push localhost:5000/multus:e2e
if [ "${MULTUS_DOCKERFILE}" != "none" ]; then
$OCI_BIN build -t localhost:5000/multus:e2e -f ../images/${MULTUS_DOCKERFILE} ..
fi
reg_host="${reg_name}"
if [ "${kind_network}" = "bridge" ]; then
reg_host="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "${reg_name}")"
fi
echo "Registry Host: ${reg_host}"
# deploy cluster with kind
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
endpoint = ["http://${reg_host}:${reg_port}"]
nodes:
- role: control-plane
- role: worker
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
pod-manifest-path: "/etc/kubernetes/manifests/"
feature-gates: "DynamicResourceAllocation=true,KubeletPodResourcesDynamicResources=true"
- role: worker
# Required by DRA Integration
##
featureGates:
DynamicResourceAllocation: true
runtimeConfig:
"api/alpha": "true"
containerdConfigPatches:
# Enable CDI as described in
# https://github.com/container-orchestrated-devices/container-device-interface#containerd-configuration
- |-
[plugins."io.containerd.grpc.v1.cri"]
enable_cdi = true
##
EOF
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:${reg_port}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
# load multus image from container host to kind node
kind load docker-image localhost:5000/multus:e2e
containers=$(docker network inspect ${kind_network} -f "{{range .Containers}}{{.Name}} {{end}}")
needs_connect="true"
for c in $containers; do
if [ "$c" = "${reg_name}" ]; then
needs_connect="false"
fi
done
if [ "${needs_connect}" = "true" ]; then
docker network connect "${kind_network}" "${reg_name}" || true
fi
worker1_pid=$($OCI_BIN inspect --format "{{ .State.Pid }}" kind-worker)
worker2_pid=$($OCI_BIN inspect --format "{{ .State.Pid }}" kind-worker2)
kind export kubeconfig
sudo env PATH=${PATH} koko -d kind-worker,eth1 -d kind-worker2,eth1
sudo env PATH=${PATH} koko -p "$worker1_pid,eth1" -p "$worker2_pid,eth1"
sleep 1
kubectl -n kube-system wait --for=condition=available deploy/coredns --timeout=300s
kubectl create -f multus-daemonset.yml
kubectl create -f yamls/$MULTUS_MANIFEST
sleep 1
kubectl -n kube-system wait --for=condition=ready -l name=multus pod --timeout=300s
kubectl create -f cni-install.yml
kubectl create -f yamls/cni-install.yml
sleep 1
kubectl -n kube-system wait --for=condition=ready -l name=cni-plugins pod --timeout=300s

11
e2e/simple-static-pod.yml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: static-web
annotations:
k8s.v1.cni.cncf.io/networks: "bridge-nad"
spec:
containers:
- name: web
image: centos:8
command: ["/bin/bash", "-c", "trap : TERM INT; sleep infinity & wait"]

15
e2e/static-pod-nad.yml Normal file
View File

@@ -0,0 +1,15 @@
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: bridge-nad
spec:
config: '{
"cniVersion": "0.3.1",
"name": "testnet",
"type": "bridge",
"bridge": "testnet0",
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}'

View File

@@ -1,10 +1,7 @@
#!/bin/sh
#set -o errexit
reg_name='kind-registry'
export PATH=${PATH}:./bin
# delete cluster kind
kind delete cluster
docker kill ${reg_name}
docker rm ${reg_name}

View File

@@ -7,9 +7,9 @@ metadata:
data:
install_cni.sh: |
cd /tmp
wget https://github.com/containernetworking/plugins/releases/download/v0.8.5/cni-plugins-linux-amd64-v0.8.5.tgz
wget https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz
cd /host/opt/cni/bin
tar xvfzp /tmp/cni-plugins-linux-amd64-v0.8.5.tgz
tar xvfzp /tmp/cni-plugins-linux-amd64-v1.4.0.tgz
sleep infinite
---
apiVersion: apps/v1

View File

@@ -5,7 +5,7 @@ metadata:
name: default-route-config
spec:
config: '{
"cniVersion": "0.3.1",
"cniVersion": "{{ CNI_VERSION }}",
"plugins": [
{
"type": "macvlan",

View File

@@ -0,0 +1,49 @@
---
apiVersion: resource.k8s.io/v1alpha2
kind: ResourceClaimTemplate
metadata:
name: gpu.example.com
spec:
spec:
resourceClassName: gpu.example.com
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: dra-net
annotations:
k8s.v1.cni.cncf.io/resourceName: gpu.example.com
spec:
config: '{
"cniVersion": "{{ CNI_VERSION }}",
"plugins": [{
"name": "mynet",
"type": "dummy",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}]
}'
---
apiVersion: v1
kind: Pod
metadata:
name: dra-integration
labels:
app: dra-integration
annotations:
k8s.v1.cni.cncf.io/networks: default/dra-net
spec:
containers:
- name: ctr0
image: ubuntu:22.04
command: ["bash", "-c"]
args: ["export; sleep 9999"]
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
source:
resourceClaimTemplateName: gpu.example.com

View File

@@ -0,0 +1,210 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
config:
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- list
- update
- watch
- apiGroups:
- ""
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-daemon-config
namespace: kube-system
labels:
tier: node
app: multus
data:
daemon-config.json: |
{
"confDir": "/host/etc/cni/net.d",
"logToStderr": true,
"logLevel": "debug",
"logFile": "/tmp/multus.log",
"binDir": "/host/opt/cni/bin",
"cniDir": "/var/lib/cni/multus",
"socketDir": "/host/run/multus",
"cniVersion": "{{ CNI_VERSION }}",
"cniConfigDir": "/host/etc/cni/net.d",
"multusConfigFile": "auto",
"forceCNIVersion": true,
"multusAutoconfigDir": "/host/etc/cni/net.d"
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-multus-ds-amd64
namespace: kube-system
labels:
tier: node
app: multus
name: multus
spec:
selector:
matchLabels:
name: multus
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
name: multus
spec:
hostNetwork: true
hostPID: true
nodeSelector:
kubernetes.io/arch: amd64
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
image: localhost:5000/multus:e2e
command: [ "/usr/src/multus-cni/bin/multus-daemon" ]
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/opt/cni/bin
- name: host-run
mountPath: /host/run
- name: host-var-lib-cni-multus
mountPath: /var/lib/cni/multus
- name: host-run-netns
mountPath: /run/netns
mountPropagation: HostToContainer
- name: multus-daemon-config
mountPath: /etc/cni/net.d/multus.d
readOnly: true
- name: kubelet-pod-resources
mountPath: /var/lib/kubelet/pod-resources
readOnly: true
env:
- name: MULTUS_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
initContainers:
- name: install-multus-shim
image: localhost:5000/multus:e2e
command:
- "cp"
- "/usr/src/multus-cni/bin/multus-shim"
- "/host/opt/cni/bin/multus-shim"
resources:
requests:
cpu: "10m"
memory: "15Mi"
securityContext:
privileged: true
volumeMounts:
- name: cnibin
mountPath: /host/opt/cni/bin
mountPropagation: Bidirectional
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /opt/cni/bin
- name: kubelet-pod-resources
hostPath:
path: /var/lib/kubelet/pod-resources
- name: multus-daemon-config
configMap:
name: multus-daemon-config
items:
- key: daemon-config.json
path: daemon-config.json
- name: host-run
hostPath:
path: /run
- name: host-var-lib-cni-multus
hostPath:
path: /var/lib/cni/multus
- name: host-run-netns
hostPath:
path: /run/netns/

View File

@@ -145,10 +145,11 @@ spec:
containers:
- name: kube-multus
image: localhost:5000/multus:e2e
command: ["/entrypoint.sh"]
command: ["/thin_entrypoint"]
args:
- "--multus-conf-file=auto"
- "--cni-version=0.3.1"
- "--force-cni-version=true"
- "--cni-version={{ CNI_VERSION }}"
resources:
requests:
cpu: "100m"
@@ -165,73 +166,23 @@ spec:
mountPath: /host/opt/cni/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /opt/cni/bin
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-multus-ds-ppc64le
namespace: kube-system
labels:
tier: node
app: multus
name: multus
spec:
selector:
matchLabels:
name: multus
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
name: multus
spec:
hostNetwork: true
nodeSelector:
kubernetes.io/arch: ppc64le
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
# ppc64le support requires multus:latest for now. support 3.3 or later.
image: nfvpe/multus:latest-ppc64le
command: ["/entrypoint.sh"]
initContainers:
- name: install-multus-binary
image: localhost:5000/multus:e2e
command: ["/install_multus"]
args:
- "--multus-conf-file=auto"
- "--cni-version=0.3.1"
- "--type"
- "thin"
resources:
requests:
cpu: "100m"
memory: "90Mi"
limits:
cpu: "100m"
memory: "90Mi"
cpu: "10m"
memory: "15Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/opt/cni/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
- name: cnibin
mountPath: /host/opt/cni/bin
mountPropagation: Bidirectional
volumes:
- name: cni
hostPath:

View File

@@ -5,7 +5,7 @@ metadata:
name: macvlan1-config
spec:
config: '{
"cniVersion": "0.3.1",
"cniVersion": "{{ CNI_VERSION }}",
"plugins": [
{
"type": "macvlan",

View File

@@ -3,11 +3,14 @@ set -o errexit
export PATH=${PATH}:./bin
kubectl create -f default-route1.yml
kubectl create -f yamls/default-route1.yml
kubectl wait --for=condition=ready -l app=default-route1 --timeout=300s pod
echo "check default-route-worker1 interface: net1"
kubectl exec default-route-worker1 -- ip a show dev net1
if [ $? -ne 0 ];then
exit 1
fi
echo "check default-route-worker1 interface address: net1"
ipaddr=$(kubectl exec default-route-worker1 -- ip -j a show | jq -r \
@@ -25,6 +28,9 @@ fi
echo "check default-route-worker2 interface: net1"
kubectl exec default-route-worker2 -- ip a show dev net1
if [ $? -ne 0 ];then
exit 1
fi
echo "check default-route-worker2 interface address: net1"
ipaddr=$(kubectl exec default-route-worker2 -- ip -j a show | jq -r \
@@ -41,4 +47,4 @@ if [ $ipaddr != "10.244.1.1" ]; then
fi
echo "cleanup resources"
kubectl delete -f default-route1.yml
kubectl delete -f yamls/default-route1.yml

60
e2e/test-dra-integration.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/sh
set -o errexit
export PATH=${PATH}:./bin
# This test is using an example implementation of a DRA driver. This driver is mocking GPU resources. At our test we
# don't care about what these resources are. We want to ensure that such resource is correctly passed in the Pod using
# Multus configurations. A couple of notes:
# - We explitictly pin the revision of the dra-example-driver to the branch `classic-dra` to indicate that the
# integration continues to work even when the dra-example-driver is updated. We know that classic-dra is supported
# in Kubernetes versions 1.26 to 1.30. Multus supports DRA in the aforementioned Kubernetes versions.
# - The chart and latest is image is not published somewhere, therefore we have to build locally. This leads to slower
# e2e suite runs.
echo "installing dra-example-driver"
repo_path="repos/dra-example-driver"
rm -rf $repo_path || true
git clone --branch classic-dra https://github.com/kubernetes-sigs/dra-example-driver.git ${repo_path}
${repo_path}/demo/build-driver.sh
KIND_CLUSTER_NAME=kind ${repo_path}/demo/scripts/load-driver-image-into-kind.sh
chart_path=${repo_path}/deployments/helm/dra-example-driver/
overriden_values_path=${chart_path}/overriden_values.yaml
# With the thick plugin, in kind, the primary network on the control plane is not always working as expected. The pods
# sometimes are not able to communicate with the control plane and the error looks like this:
# failed to list *v1alpha2.PodSchedulingContext: Get "https://10.96.0.1:443/apis/resource.k8s.io/v1alpha2/podschedulingcontexts?limit=500&resourceVersion=0": dial tcp 10.96.0.1:443: connect: no route to host
# We override the values here to schedule the controller on the worker nodes where the network is working as expected.
cat <<EOF >> ${overriden_values_path}
controller:
nodeSelector: null
tolerations: null
EOF
helm install \
-n dra-example-driver \
--create-namespace \
-f ${overriden_values_path} \
dra-example-driver \
${chart_path}
echo "installing testing pods"
kubectl create -f yamls/dra-integration.yml
kubectl wait --for=condition=ready -l app=dra-integration --timeout=300s pod
echo "check dra-integration pod for DRA injected environment variable"
# We can validate that the resource is correctly injected by checking an environment variable this dra driver is injecting
# in the Pod.
# https://github.com/kubernetes-sigs/dra-example-driver/blob/be2b8b1db47b8c757440e955ce5ced88c23bfe86/cmd/dra-example-kubeletplugin/cdi.go#L71C20-L71C44
env_variable=$(kubectl exec dra-integration -- bash -c "echo \$DRA_RESOURCE_DRIVER_NAME | grep gpu.resource.example.com")
if [ $? -eq 0 ];then
echo "dra-integration pod has DRA injected environment variable"
else
echo "dra-integration pod doesn't have DRA injected environment variable"
exit 1
fi
echo "cleanup resources"
kubectl delete -f yamls/dra-integration.yml
helm uninstall -n dra-example-driver dra-example-driver

View File

@@ -3,11 +3,17 @@ set -o errexit
export PATH=${PATH}:./bin
kubectl create -f simple-macvlan1.yml
kubectl create -f yamls/simple-macvlan1.yml
kubectl wait --for=condition=ready -l app=macvlan --timeout=300s pod
echo "check macvlan1-worker1 interface: net1"
kubectl exec macvlan1-worker1 -- ip a show dev net1
net=$(kubectl exec macvlan1-worker1 -- ip a show dev net1)
if [ $? -eq 0 ];then
echo "macvlan1-worker1 pod has net1 card"
else
echo "macvlan1-worker1 pod has no net1 card"
exit 1
fi
echo "check macvlan1-worker1 interface address: net1"
ipaddr=$(kubectl exec macvlan1-worker1 -- ip -j a show | jq -r \
@@ -17,7 +23,13 @@ if [ $ipaddr != "10.1.1.11" ]; then
fi
echo "check macvlan1-worker2 interface: net1"
kubectl exec macvlan1-worker2 -- ip a show dev net1
net2=$(kubectl exec macvlan1-worker2 -- ip a show dev net1)
if [ $? -eq 0 ];then
echo "macvlan1-worker2 pod has net1 card"
else
echo "macvlan1-worker2 pod has no net1 card"
exit 1
fi
echo "check macvlan1-worker2 interface address: net1"
ipaddr=$(kubectl exec macvlan1-worker2 -- ip -j a show | jq -r \
@@ -27,4 +39,4 @@ if [ $ipaddr != "10.1.1.12" ]; then
fi
echo "cleanup resources"
kubectl delete -f simple-macvlan1.yml
kubectl delete -f yamls/simple-macvlan1.yml

View File

@@ -3,8 +3,8 @@ set -o errexit
export PATH=${PATH}:./bin
kubectl create -f simple-pod.yml
kubectl create -f yamls/simple-pod.yml
kubectl wait --for=condition=ready -l app=simple --timeout=300s pod
echo "cleanup resources"
kubectl delete -f simple-pod.yml
kubectl delete -f yamls/simple-pod.yml

22
e2e/test-static-pod.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -o errexit
echo "Creating network attachment definition"
kubectl create -f static-pod-nad.yml
echo "Creating static pod config file"
docker cp simple-static-pod.yml kind-worker:/etc/kubernetes/manifests/static-web.yaml
echo "Waiting for static pod to start"
kubectl wait --for=condition=Ready --namespace=default pod/static-web-kind-worker
echo "Checking the pod annotation for net1 interface"
kubectl exec static-web-kind-worker --namespace=default -- ip a show dev net1
echo "Deleting static pod"
docker exec kind-worker /bin/bash -c "rm /etc/kubernetes/manifests/static-web.yaml"
echo "Deleting network attachment definition"
kubectl delete -f static-pod-nad.yml
echo "Test complete"

View File

@@ -54,3 +54,4 @@ spec:
image: dougbtv/centos-network
ports:
- containerPort: 80
automountServiceAccountToken: false

View File

@@ -45,3 +45,4 @@ spec:
limits:
intel.com/sriov: '1'
restartPolicy: "Never"
automountServiceAccountToken: false

113
go.mod
View File

@@ -1,51 +1,78 @@
module gopkg.in/k8snetworkplumbingwg/multus-cni.v3
module gopkg.in/k8snetworkplumbingwg/multus-cni.v4
go 1.16
go 1.21
require (
github.com/containernetworking/cni v0.8.1
github.com/containernetworking/plugins v0.9.1
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.1.1-0.20210510153419-66a699ae3b05
github.com/onsi/ginkgo v1.12.1
github.com/onsi/gomega v1.10.3
github.com/pkg/errors v0.9.1
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
google.golang.org/grpc v1.27.1
github.com/blang/semver v3.5.1+incompatible
github.com/containernetworking/cni v1.2.0-rc1
github.com/containernetworking/plugins v1.1.0
github.com/fsnotify/fsnotify v1.6.0
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.5
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/prometheus/client_golang v1.16.0
github.com/spf13/pflag v1.0.5
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
golang.org/x/net v0.23.0
golang.org/x/sys v0.18.0
google.golang.org/grpc v1.58.3
gopkg.in/natefinch/lumberjack.v2 v2.0.0
k8s.io/api v0.20.10
k8s.io/apimachinery v0.20.10
k8s.io/client-go v0.20.10
k8s.io/api v0.29.0
k8s.io/apimachinery v0.29.0
k8s.io/client-go v0.29.0
k8s.io/klog v1.0.0
k8s.io/kubelet v0.0.0
k8s.io/kubernetes v1.20.10
k8s.io/klog/v2 v2.110.1
k8s.io/kubelet v0.27.5
)
replace (
github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
k8s.io/api => k8s.io/api v0.20.10
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.10
k8s.io/apimachinery => k8s.io/apimachinery v0.20.10
k8s.io/apiserver => k8s.io/apiserver v0.20.10
k8s.io/cli-runtime => k8s.io/cli-runtime v0.20.10
k8s.io/client-go => k8s.io/client-go v0.20.10
k8s.io/cloud-provider => k8s.io/cloud-provider v0.20.10
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.20.10
k8s.io/code-generator => k8s.io/code-generator v0.20.10
k8s.io/component-base => k8s.io/component-base v0.20.10
k8s.io/component-helpers => k8s.io/component-helpers v0.20.10
k8s.io/controller-manager => k8s.io/controller-manager v0.20.10
k8s.io/cri-api => k8s.io/cri-api v0.20.10
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.20.10
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.20.10
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.20.10
k8s.io/kube-proxy => k8s.io/kube-proxy v0.20.10
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.20.10
k8s.io/kubectl => k8s.io/kubectl v0.20.10
k8s.io/kubelet => k8s.io/kubelet v0.20.10
k8s.io/kubernetes => k8s.io/kubernetes v1.20.10
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.20.10
k8s.io/metrics => k8s.io/metrics v0.20.10
k8s.io/mount-utils => k8s.io/mount-utils v0.20.10
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.20.10
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // 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/yaml v1.3.0 // indirect
)

1194
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -7,48 +7,76 @@ if [ ! -d ${DEST_DIR} ]; then
mkdir ${DEST_DIR}
fi
# Add version/commit/date into binary
# In case of TravisCI, need to check error code of 'git describe'.
if [ -z "$VERSION" ]; then
# Specify correspondingGOARCH from TARGETPLATFORM
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then
export GOARCH=amd64
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then
export GOARCH=arm64
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then
export GOARCH=arm
elif [ "$TARGETPLATFORM" = "linux/ppc64le" ]; then
export GOARCH=ppc64le
elif [ "$TARGETPLATFORM" = "linux/s390x" ]; then
export GOARCH=s390x
fi
# version information
hasGit=true
git version > /dev/null 2>&1 || hasGit=false
GIT_SHA=""
GIT_TREE_STATE=""
GIT_TAG=""
GIT_TAG_LAST=""
RELEASE_STATUS=""
if $hasGit; then
set +e
git describe --tags --abbrev=0 > /dev/null 2>&1
if [ "$?" != "0" ]; then
VERSION="master"
else
VERSION=$(git describe --tags --abbrev=0)
fi
GIT_SHA=$(git rev-parse --short HEAD)
# Tree state is "dirty" if there are uncommitted changes, untracked files are ignored
GIT_TREE_STATE=$(test -n "`git status --porcelain --untracked-files=no`" && echo "dirty" || echo "clean")
# Empty string if we are not building a tag
GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || true)
# Find most recent tag
GIT_TAG_LAST=$(git describe --tags --abbrev=0 2>/dev/null || true)
set -e
fi
# VERSION override mechanism if needed
VERSION=${VERSION:-}
if [[ -n "${VERSION}" || -n "${GIT_TAG}" ]]; then
RELEASE_STATUS=",released"
fi
if [ -z "$VERSION" ]; then
VERSION=$GIT_TAG_LAST
fi
# Add version/commit/date into binary
DATE=$(date -u -d "@${SOURCE_DATE_EPOCH:-$(date +%s)}" --iso-8601=seconds)
COMMIT=${COMMIT:-$(git rev-parse --verify HEAD)}
LDFLAGS="-X main.version=${VERSION:-master} -X main.commit=${COMMIT} -X main.date=${DATE}"
export CGO_ENABLED=0
LDFLAGS="-X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.version=${VERSION} \
-X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.commit=${COMMIT} \
-X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.gitTreeState=${GIT_TREE_STATE} \
-X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.releaseStatus=${RELEASE_STATUS} \
-X gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/multus.date=${DATE}"
export CGO_ENABLED=${CGO_ENABLED:-0}
# this if... will be removed when gomodules goes default
if [ "$GO111MODULE" == "off" ]; then
echo "Building plugin without go module"
echo "Warning: this will be deprecated in near future so please use go modules!"
# build with go modules
export GO111MODULE=on
ORG_PATH="gopkg.in/k8snetworkplumbingwg"
REPO_PATH="${ORG_PATH}/multus-cni.v3"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
go build -o ${PWD}/bin/multus -tags no_openssl -ldflags "${LDFLAGS}" "$@" ${REPO_PATH}/cmd
else
# build with go modules
export GO111MODULE=on
BUILD_ARGS=(-o ${DEST_DIR}/multus -tags no_openssl)
if [ -n "$MODMODE" ]; then
BUILD_ARGS+=(-mod "$MODMODE")
fi
echo "Building plugins"
go build ${BUILD_ARGS[*]} -ldflags "${LDFLAGS}" "$@" ./cmd
if [ -n "$MODMODE" ]; then
BUILD_ARGS=(-mod "$MODMODE")
fi
echo "Building multus"
go build -o ${DEST_DIR}/multus ${BUILD_ARGS} -ldflags "${LDFLAGS}" "$@" ./cmd/multus
echo "Building multus-daemon"
go build -o "${DEST_DIR}"/multus-daemon ${BUILD_ARGS} -ldflags "${LDFLAGS}" ./cmd/multus-daemon
echo "Building multus-shim"
go build -o "${DEST_DIR}"/multus-shim ${BUILD_ARGS} -ldflags "${LDFLAGS}" ./cmd/multus-shim
echo "Building install_multus"
go build -o "${DEST_DIR}"/install_multus ${BUILD_ARGS} -ldflags "${LDFLAGS}" ./cmd/install_multus
echo "Building thin_entrypoint"
go build -o "${DEST_DIR}"/thin_entrypoint ${BUILD_ARGS} -ldflags "${LDFLAGS}" ./cmd/thin_entrypoint
echo "Building kubeconfig_generator"
go build -o "${DEST_DIR}"/kubeconfig_generator ${BUILD_ARGS} -ldflags "${LDFLAGS}" ./cmd/kubeconfig_generator
echo "Building cert-approver"
go build -o "${DEST_DIR}"/cert-approver ${BUILD_ARGS} -ldflags "${LDFLAGS}" ./cmd/cert-approver

View File

@@ -19,5 +19,5 @@ if [ "$GO111MODULE" == "off" ]; then
bash -c "umask 0; cd ${GOPATH}/src/${REPO_PATH}; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test -v -covermode=count -coverprofile=coverage.out ./..."
else
# test with go modules
bash -c "umask 0; go test -v -covermode=count -coverprofile=coverage.out ./..."
bash -c "umask 0; go test -v -race -covermode=atomic -coverprofile=coverage.out ./..."
fi

21
images/Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
# This Dockerfile is used to build the image available on DockerHub
FROM --platform=$BUILDPLATFORM golang:1.23 as build
# Add everything
ADD . /usr/src/multus-cni
ARG TARGETPLATFORM
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
FROM gcr.io/distroless/base-debian11:latest
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni/bin /usr/src/multus-cni/bin
COPY --from=build /usr/src/multus-cni/LICENSE /usr/src/multus-cni/LICENSE
WORKDIR /
COPY --from=build /usr/src/multus-cni/bin/install_multus /
COPY --from=build /usr/src/multus-cni/bin/thin_entrypoint /
COPY --from=build /usr/src/multus-cni/bin/kubeconfig_generator /
COPY --from=build /usr/src/multus-cni/bin/cert-approver /
CMD ["/thin_entrypoint"]

21
images/Dockerfile.debug Normal file
View File

@@ -0,0 +1,21 @@
# This Dockerfile is used to build the image available on DockerHub
FROM --platform=$BUILDPLATFORM golang:1.23 as build
# Add everything
ADD . /usr/src/multus-cni
ARG TARGETPLATFORM
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
FROM gcr.io/distroless/base-debian11:debug
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni/bin /usr/src/multus-cni/bin
COPY --from=build /usr/src/multus-cni/LICENSE /usr/src/multus-cni/LICENSE
WORKDIR /
COPY --from=build /usr/src/multus-cni/bin/install_multus /
COPY --from=build /usr/src/multus-cni/bin/thin_entrypoint /
COPY --from=build /usr/src/multus-cni/bin/kubeconfig_generator /
COPY --from=build /usr/src/multus-cni/bin/cert-approver /
CMD ["/thin_entrypoint"]

View File

@@ -1,5 +1,6 @@
# This dockerfile is specific to building Multus for OpenShift
FROM openshift/origin-release:golang-1.16 as builder
# The okd-builder image is locally built from https://raw.githubusercontent.com/okd-project/images/main/okd-builder.Dockerfile
FROM local/okdbuilder:latest as builder
ADD . /usr/src/multus-cni
@@ -7,15 +8,16 @@ WORKDIR /usr/src/multus-cni
ENV GO111MODULE=off
RUN ./hack/build-go.sh
FROM openshift/origin-base
FROM quay.io/openshift/origin-base:latest
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
RUN mkdir -p /usr/src/multus-cni/images && mkdir -p /usr/src/multus-cni/bin
COPY --from=builder /usr/src/multus-cni/bin/multus /usr/src/multus-cni/bin
ADD ./images/entrypoint.sh /
COPY --from=builder /usr/src/multus-cni/bin/install_multus /
COPY --from=builder /usr/src/multus-cni/bin/thin_entrypoint /
LABEL io.k8s.display-name="Multus CNI" \
io.k8s.description="This is a component of OpenShift Container Platform and provides a meta CNI plugin." \
io.openshift.tags="openshift" \
maintainer="Doug Smith <dosmith@redhat.com>"
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["/thin_entrypoint"]

17
images/Dockerfile.thick Normal file
View File

@@ -0,0 +1,17 @@
# This Dockerfile is used to build the image available on DockerHub
FROM golang:1.23 as build
# Add everything
ADD . /usr/src/multus-cni
RUN cd /usr/src/multus-cni && \
./hack/build-go.sh
FROM debian:stable-slim
LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/multus-cni
COPY --from=build /usr/src/multus-cni/bin /usr/src/multus-cni/bin
COPY --from=build /usr/src/multus-cni/LICENSE /usr/src/multus-cni/LICENSE
COPY --from=build /usr/src/multus-cni/bin/cert-approver /
WORKDIR /
ENTRYPOINT [ "/usr/src/multus-cni/bin/multus-daemon" ]

View File

@@ -5,7 +5,7 @@ This is used for distribution of Multus in a Docker image.
Typically you'd build this from the root of your Multus clone, as such:
```
$ docker build -t dougbtv/multus .
$ docker build -t dougbtv/multus -f images/Dockerfile .
```
---
@@ -15,7 +15,7 @@ $ docker build -t dougbtv/multus .
You may wish to deploy Multus as a daemonset, you can do so by starting with the example Daemonset shown here:
```
$ kubectl create -f ./images/multus-daemonset.yml
$ kubectl create -f ./deployments/multus-daemonset.yml
```
Note: The likely best practice here is to build your own image given the Dockerfile, and then push it to your preferred registry, and change the `image` fields in the Daemonset YAML to reference that image.
@@ -41,9 +41,7 @@ in lexicographical order in cni-conf-dir).
./entrypoint.sh
-h --help
--cni-conf-dir=/host/etc/cni/net.d
--cni-bin-dir=/host/opt/cni/bin
--multus-conf-file=/usr/src/multus-cni/images/70-multus.conf
--multus-bin-file=/usr/src/multus-cni/bin/multus
--multus-kubeconfig-file-host=/etc/cni/net.d/multus.d/multus.kubeconfig
```

View File

@@ -1,263 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
description: 'NetworkAttachmentDefinition is a CRD schema specified by the Network Plumbing
Working Group to express the intent for attaching pods to one or more logical or physical
networks. More information available at: https://github.com/k8snetworkplumbingwg/multi-net-spec'
type: object
properties:
spec:
description: 'NetworkAttachmentDefinition spec defines the desired state of a network attachment'
type: object
properties:
config:
description: 'NetworkAttachmentDefinition config is a JSON-formatted CNI configuration'
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
- apiGroups:
- ""
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-cni-config
namespace: kube-system
labels:
tier: node
app: multus
data:
# NOTE: If you'd prefer to manually apply a configuration file, you may create one here.
# In the case you'd like to customize the Multus installation, you should change the arguments to the Multus pod
# change the "args" line below from
# - "--multus-conf-file=auto"
# to:
# "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
# Additionally -- you should ensure that the name "70-multus.conf" is the alphabetically first name in the
# /etc/cni/net.d/ directory on each node, otherwise, it will not be used by the Kubelet.
cni-conf.json: |
{
"name": "multus-cni-network",
"type": "multus",
"capabilities": {
"portMappings": true
},
"delegates": [
{
"cniVersion": "0.3.1",
"name": "default-cni-network",
"plugins": [
{
"type": "flannel",
"name": "flannel.1",
"delegate": {
"isDefaultGateway": true,
"hairpinMode": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
],
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
}
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-amd64
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
# crio support requires multus:latest for now. support 3.3 or later.
image: nfvpe/multus:v3.6
command: ["/entrypoint.sh"]
args:
- "--cni-bin-dir=/host/usr/libexec/cni"
- "--multus-conf-file=auto"
- "--override-network-name=true"
- "--restart-crio=true"
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
volumeMounts:
- name: run
mountPath: /run
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/usr/libexec/cni
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: run
hostPath:
path: /run
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /usr/libexec/cni
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-ppc64le
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: ppc64le
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
# crio support requires multus:latest for now. support 3.3 or later.
image: nfvpe/multus:latest-ppc64le
command: ["/entrypoint.sh"]
args:
- "--cni-bin-dir=/host/usr/libexec/cni"
- "--multus-conf-file=auto"
- "--override-network-name=true"
- "--restart-crio=true"
resources:
requests:
cpu: "100m"
memory: "90Mi"
limits:
cpu: "100m"
memory: "90Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/usr/libexec/cni
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /usr/libexec/cni
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf

View File

@@ -1,232 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
version: v1
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
validation:
openAPIV3Schema:
properties:
spec:
properties:
config:
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-cni-config
namespace: kube-system
labels:
tier: node
app: multus
data:
# NOTE: If you'd prefer to manually apply a configuration file, you may create one here.
# In the case you'd like to customize the Multus installation, you should change the arguments to the Multus pod
# change the "args" line below from
# - "--multus-conf-file=auto"
# to:
# "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
# Additionally -- you should ensure that the name "70-multus.conf" is the alphabetically first name in the
# /etc/cni/net.d/ directory on each node, otherwise, it will not be used by the Kubelet.
cni-conf.json: |
{
"name": "multus-cni-network",
"type": "multus",
"capabilities": {
"portMappings": true
},
"delegates": [
{
"cniVersion": "0.3.1",
"name": "default-cni-network",
"plugins": [
{
"type": "flannel",
"name": "flannel.1",
"delegate": {
"isDefaultGateway": true,
"hairpinMode": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
],
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
}
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-amd64
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
image: nfvpe/multus:v3.6
command: ["/entrypoint.sh"]
args:
- "--multus-conf-file=auto"
- "--cni-bin-dir=/host/home/kubernetes/bin"
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/home/kubernetes/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /home/kubernetes/bin
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-ppc64le
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: ppc64le
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
# ppc64le support requires multus:latest for now. support 3.3 or later.
image: nfvpe/multus:latest-ppc64le
command: ["/entrypoint.sh"]
args:
- "--multus-conf-file=auto"
- "--cni-bin-dir=/host/home/kubernetes/bin"
resources:
requests:
cpu: "100m"
memory: "90Mi"
limits:
cpu: "100m"
memory: "90Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/home/kubernetes/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /home/kubernetes/bin
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf

View File

@@ -1,249 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
description: 'NetworkAttachmentDefinition is a CRD schema specified by the Network Plumbing
Working Group to express the intent for attaching pods to one or more logical or physical
networks. More information available at: https://github.com/k8snetworkplumbingwg/multi-net-spec'
type: object
properties:
spec:
description: 'NetworkAttachmentDefinition spec defines the desired state of a network attachment'
type: object
properties:
config:
description: 'NetworkAttachmentDefinition config is a JSON-formatted CNI configuration'
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
- apiGroups:
- ""
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-cni-config
namespace: kube-system
labels:
tier: node
app: multus
data:
# NOTE: If you'd prefer to manually apply a configuration file, you may create one here.
# In the case you'd like to customize the Multus installation, you should change the arguments to the Multus pod
# change the "args" line below from
# - "--multus-conf-file=auto"
# to:
# "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
# Additionally -- you should ensure that the name "70-multus.conf" is the alphabetically first name in the
# /etc/cni/net.d/ directory on each node, otherwise, it will not be used by the Kubelet.
cni-conf.json: |
{
"name": "multus-cni-network",
"type": "multus",
"capabilities": {
"portMappings": true
},
"delegates": [
{
"cniVersion": "0.3.1",
"name": "default-cni-network",
"plugins": [
{
"type": "flannel",
"name": "flannel.1",
"delegate": {
"isDefaultGateway": true,
"hairpinMode": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
],
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
}
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-amd64
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: amd64
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
image: nfvpe/multus:v3.6
command: ["/entrypoint.sh"]
args:
- "--multus-conf-file=auto"
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/opt/cni/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /opt/cni/bin
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: kube-multus-ds-ppc64le
namespace: kube-system
labels:
tier: node
app: multus
spec:
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
spec:
hostNetwork: true
nodeSelector:
beta.kubernetes.io/arch: ppc64le
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
# ppc64le support requires multus:latest for now. support 3.3 or later.
image: nfvpe/multus:latest-ppc64le
command: ["/entrypoint.sh"]
args:
- "--multus-conf-file=auto"
resources:
requests:
cpu: "100m"
memory: "90Mi"
limits:
cpu: "100m"
memory: "90Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/opt/cni/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /opt/cni/bin
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf

View File

@@ -1,455 +0,0 @@
#!/bin/bash
# Always exit on errors.
set -e
# Trap sigterm
function exitonsigterm() {
echo "Trapped sigterm, exiting."
exit 0
}
trap exitonsigterm SIGTERM
# Set our known directories.
CNI_CONF_DIR="/host/etc/cni/net.d"
CNI_BIN_DIR="/host/opt/cni/bin"
ADDITIONAL_BIN_DIR=""
MULTUS_CONF_FILE="/usr/src/multus-cni/images/70-multus.conf"
MULTUS_AUTOCONF_DIR="/host/etc/cni/net.d"
MULTUS_BIN_FILE="/usr/src/multus-cni/bin/multus"
MULTUS_KUBECONFIG_FILE_HOST="/etc/cni/net.d/multus.d/multus.kubeconfig"
MULTUS_TEMP_KUBECONFIG="/tmp/multus.kubeconfig"
MULTUS_MASTER_CNI_FILE_NAME=""
MULTUS_NAMESPACE_ISOLATION=false
MULTUS_GLOBAL_NAMESPACES=""
MULTUS_LOG_TO_STDERR=true
MULTUS_LOG_LEVEL=""
MULTUS_LOG_FILE=""
MULTUS_READINESS_INDICATOR_FILE=""
OVERRIDE_NETWORK_NAME=false
MULTUS_CLEANUP_CONFIG_ON_EXIT=false
RESTART_CRIO=false
CRIO_RESTARTED_ONCE=false
RENAME_SOURCE_CONFIG_FILE=false
SKIP_BINARY_COPY=false
# Give help text for parameters.
function usage()
{
echo -e "This is an entrypoint script for Multus CNI to overlay its binary and "
echo -e "configuration into locations in a filesystem. The configuration & binary file "
echo -e "will be copied to the corresponding configuration directory. When "
echo -e "'--multus-conf-file=auto' is used, 00-multus.conf will be automatically "
echo -e "generated from the CNI configuration file of the master plugin (the first file "
echo -e "in lexicographical order in cni-conf-dir). When '--multus-master-cni-file-name'"
echo -e "is used, 00-multus.conf will only be automatically generated from the specific"
echo -e "file rather than the first file."
echo -e ""
echo -e "./entrypoint.sh"
echo -e "\t-h --help"
echo -e "\t--cni-conf-dir=$CNI_CONF_DIR"
echo -e "\t--cni-bin-dir=$CNI_BIN_DIR"
echo -e "\t--cni-version=<cniVersion (e.g. 0.3.1)>"
echo -e "\t--multus-conf-file=$MULTUS_CONF_FILE"
echo -e "\t--multus-bin-file=$MULTUS_BIN_FILE"
echo -e "\t--skip-multus-binary-copy=$SKIP_BINARY_COPY"
echo -e "\t--multus-kubeconfig-file-host=$MULTUS_KUBECONFIG_FILE_HOST"
echo -e "\t--multus-master-cni-file-name=$MULTUS_MASTER_CNI_FILE_NAME (empty by default, example: 10-calico.conflist)"
echo -e "\t--namespace-isolation=$MULTUS_NAMESPACE_ISOLATION"
echo -e "\t--global-namespaces=$MULTUS_GLOBAL_NAMESPACES (used only with --namespace-isolation=true)"
echo -e "\t--multus-autoconfig-dir=$MULTUS_AUTOCONF_DIR (used only with --multus-conf-file=auto)"
echo -e "\t--multus-log-to-stderr=$MULTUS_LOG_TO_STDERR (empty by default, used only with --multus-conf-file=auto)"
echo -e "\t--multus-log-level=$MULTUS_LOG_LEVEL (empty by default, used only with --multus-conf-file=auto)"
echo -e "\t--multus-log-file=$MULTUS_LOG_FILE (empty by default, used only with --multus-conf-file=auto)"
echo -e "\t--override-network-name=false (used only with --multus-conf-file=auto)"
echo -e "\t--cleanup-config-on-exit=false (used only with --multus-conf-file=auto)"
echo -e "\t--rename-conf-file=false (used only with --multus-conf-file=auto)"
echo -e "\t--readiness-indicator-file=$MULTUS_READINESS_INDICATOR_FILE (used only with --multus-conf-file=auto)"
echo -e "\t--additional-bin-dir=$ADDITIONAL_BIN_DIR (adds binDir option to configuration, used only with --multus-conf-file=auto)"
echo -e "\t--restart-crio=false (restarts CRIO after config file is generated)"
}
function log()
{
echo "$(date --iso-8601=seconds) ${1}"
}
function error()
{
log "ERR: {$1}"
}
function warn()
{
log "WARN: {$1}"
}
if ! type python3 &> /dev/null; then
alias python=python3
fi
# Parse parameters given as arguments to this script.
while [ "$1" != "" ]; do
PARAM=`echo $1 | awk -F= '{print $1}'`
VALUE=`echo $1 | awk -F= '{print $2}'`
case $PARAM in
-h | --help)
usage
exit
;;
--cni-version)
CNI_VERSION=$VALUE
;;
--cni-conf-dir)
CNI_CONF_DIR=$VALUE
;;
--cni-bin-dir)
CNI_BIN_DIR=$VALUE
;;
--multus-conf-file)
MULTUS_CONF_FILE=$VALUE
;;
--multus-bin-file)
MULTUS_BIN_FILE=$VALUE
;;
--multus-kubeconfig-file-host)
MULTUS_KUBECONFIG_FILE_HOST=$VALUE
;;
--multus-master-cni-file-name)
MULTUS_MASTER_CNI_FILE_NAME=$VALUE
;;
--namespace-isolation)
MULTUS_NAMESPACE_ISOLATION=$VALUE
;;
--global-namespaces)
MULTUS_GLOBAL_NAMESPACES=$VALUE
;;
--multus-log-to-stderr)
MULTUS_LOG_TO_STDERR=$VALUE
;;
--multus-log-level)
MULTUS_LOG_LEVEL=$VALUE
;;
--multus-log-file)
MULTUS_LOG_FILE=$VALUE
;;
--multus-autoconfig-dir)
MULTUS_AUTOCONF_DIR=$VALUE
;;
--override-network-name)
OVERRIDE_NETWORK_NAME=$VALUE
;;
--cleanup-config-on-exit)
MULTUS_CLEANUP_CONFIG_ON_EXIT=$VALUE
;;
--restart-crio)
RESTART_CRIO=$VALUE
;;
--rename-conf-file)
RENAME_SOURCE_CONFIG_FILE=$VALUE
;;
--additional-bin-dir)
ADDITIONAL_BIN_DIR=$VALUE
;;
--skip-multus-binary-copy)
SKIP_BINARY_COPY=$VALUE
;;
--readiness-indicator-file)
MULTUS_READINESS_INDICATOR_FILE=$VALUE
;;
*)
warn "unknown parameter \"$PARAM\""
;;
esac
shift
done
# Create array of known locations
declare -a arr=($CNI_CONF_DIR $CNI_BIN_DIR $MULTUS_BIN_FILE)
if [ "$MULTUS_CONF_FILE" != "auto" ]; then
arr+=($MULTUS_CONF_FILE)
fi
# Loop through and verify each location each.
for i in "${arr[@]}"
do
if [ ! -e "$i" ]; then
warn "Location $i does not exist"
exit 1;
fi
done
# Copy files into place and atomically move into final binary name
if [ "$SKIP_BINARY_COPY" = false ]; then
cp -f $MULTUS_BIN_FILE $CNI_BIN_DIR/_multus
mv -f $CNI_BIN_DIR/_multus $CNI_BIN_DIR/multus
else
log "Entrypoint skipped copying Multus binary."
fi
if [ "$MULTUS_CONF_FILE" != "auto" ]; then
cp -f $MULTUS_CONF_FILE $CNI_CONF_DIR
fi
# Make a multus.d directory (for our kubeconfig)
mkdir -p $CNI_CONF_DIR/multus.d
MULTUS_KUBECONFIG=$CNI_CONF_DIR/multus.d/multus.kubeconfig
# ------------------------------- Generate a "kube-config"
# Inspired by: https://tinyurl.com/y7r2knme
SERVICE_ACCOUNT_PATH=/var/run/secrets/kubernetes.io/serviceaccount
KUBE_CA_FILE=${KUBE_CA_FILE:-$SERVICE_ACCOUNT_PATH/ca.crt}
SERVICEACCOUNT_TOKEN=$(cat $SERVICE_ACCOUNT_PATH/token)
SKIP_TLS_VERIFY=${SKIP_TLS_VERIFY:-false}
# Check if we're running as a k8s pod.
if [ -f "$SERVICE_ACCOUNT_PATH/token" ]; then
# We're running as a k8d pod - expect some variables.
if [ -z ${KUBERNETES_SERVICE_HOST} ]; then
error "KUBERNETES_SERVICE_HOST not set"; exit 1;
fi
if [ -z ${KUBERNETES_SERVICE_PORT} ]; then
error "KUBERNETES_SERVICE_PORT not set"; exit 1;
fi
if [ "$SKIP_TLS_VERIFY" == "true" ]; then
TLS_CFG="insecure-skip-tls-verify: true"
elif [ -f "$KUBE_CA_FILE" ]; then
TLS_CFG="certificate-authority-data: $(cat $KUBE_CA_FILE | base64 | tr -d '\n')"
fi
# Write a kubeconfig file for the CNI plugin. Do this
# to skip TLS verification for now. We should eventually support
# writing more complete kubeconfig files. This is only used
# if the provided CNI network config references it.
touch $MULTUS_TEMP_KUBECONFIG
chmod ${KUBECONFIG_MODE:-600} $MULTUS_TEMP_KUBECONFIG
# Write the kubeconfig to a temp file first.
cat > $MULTUS_TEMP_KUBECONFIG <<EOF
# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: ${KUBERNETES_SERVICE_PROTOCOL:-https}://[${KUBERNETES_SERVICE_HOST}]:${KUBERNETES_SERVICE_PORT}
$TLS_CFG
users:
- name: multus
user:
token: "${SERVICEACCOUNT_TOKEN}"
contexts:
- name: multus-context
context:
cluster: local
user: multus
current-context: multus-context
EOF
# Atomically move the temp kubeconfig to its permanent home.
mv -f $MULTUS_TEMP_KUBECONFIG $MULTUS_KUBECONFIG
else
warn "Doesn't look like we're running in a kubernetes environment (no serviceaccount token)"
fi
# ---------------------- end Generate a "kube-config".
# ------------------------------- Generate "00-multus.conf"
function generateMultusConf {
if [ "$MULTUS_CONF_FILE" == "auto" ]; then
log "Generating Multus configuration file using files in $MULTUS_AUTOCONF_DIR..."
found_master=false
tries=0
while [ $found_master == false ]; do
if [ "$MULTUS_MASTER_CNI_FILE_NAME" != "" ]; then
MASTER_PLUGIN="$MULTUS_MASTER_CNI_FILE_NAME"
else
MASTER_PLUGIN="$(ls $MULTUS_AUTOCONF_DIR | grep -E '\.conf(list)?$' | grep -Ev '00-multus\.conf' | head -1)"
fi
if [ "$MASTER_PLUGIN" == "" ]; then
if [ $tries -lt 600 ]; then
if ! (($tries % 5)); then
log "Attempting to find master plugin configuration, attempt $tries"
fi
let "tries+=1"
sleep 1;
else
error "Multus could not be configured: no master plugin was found."
exit 1;
fi
else
log "Using MASTER_PLUGIN: $MASTER_PLUGIN"
found_master=true
ISOLATION_STRING=""
if [ "$MULTUS_NAMESPACE_ISOLATION" == true ]; then
ISOLATION_STRING="\"namespaceIsolation\": true,"
fi
GLOBAL_NAMESPACES_STRING=""
if [ ! -z "${MULTUS_GLOBAL_NAMESPACES// }" ]; then
GLOBAL_NAMESPACES_STRING="\"globalNamespaces\": \"$MULTUS_GLOBAL_NAMESPACES\","
fi
LOG_TO_STDERR_STRING=""
if [ "$MULTUS_LOG_TO_STDERR" == false ]; then
LOG_TO_STDERR_STRING="\"logToStderr\": false,"
fi
LOG_LEVEL_STRING=""
if [ ! -z "${MULTUS_LOG_LEVEL// }" ]; then
case "$MULTUS_LOG_LEVEL" in
debug)
;;
error)
;;
panic)
;;
verbose)
;;
*)
error "Log levels should be one of: debug/verbose/error/panic, did not understand $MULTUS_LOG_LEVEL"
usage
exit 1
esac
LOG_LEVEL_STRING="\"logLevel\": \"$MULTUS_LOG_LEVEL\","
fi
LOG_FILE_STRING=""
if [ ! -z "${MULTUS_LOG_FILE// }" ]; then
LOG_FILE_STRING="\"logFile\": \"$MULTUS_LOG_FILE\","
fi
CNI_VERSION_STRING=""
if [ ! -z "${CNI_VERSION// }" ]; then
CNI_VERSION_STRING="\"cniVersion\": \"$CNI_VERSION\","
fi
ADDITIONAL_BIN_DIR_STRING=""
if [ ! -z "${ADDITIONAL_BIN_DIR// }" ]; then
ADDITIONAL_BIN_DIR_STRING="\"binDir\": \"$ADDITIONAL_BIN_DIR\","
fi
READINESS_INDICATOR_FILE_STRING=""
if [ ! -z "${MULTUS_READINESS_INDICATOR_FILE// }" ]; then
READINESS_INDICATOR_FILE_STRING="\"readinessindicatorfile\": \"$MULTUS_READINESS_INDICATOR_FILE\","
fi
if [ "$OVERRIDE_NETWORK_NAME" == "true" ]; then
MASTER_PLUGIN_NET_NAME="$(cat $MULTUS_AUTOCONF_DIR/$MASTER_PLUGIN | \
python -c 'import json,sys;print(json.load(sys.stdin)["name"])')"
else
MASTER_PLUGIN_NET_NAME="multus-cni-network"
fi
capabilities_python_filter_tmpfile=$(mktemp)
cat << EOF > $capabilities_python_filter_tmpfile
import json,sys
conf = json.load(sys.stdin)
capabilities = {}
if 'plugins' in conf:
for capa in [p['capabilities'] for p in conf['plugins'] if 'capabilities' in p]:
capabilities.update({capability:enabled for (capability,enabled) in capa.items() if enabled})
elif 'capabilities' in conf:
capabilities.update({capability:enabled for (capability,enabled) in conf['capabilities'] if enabled})
if len(capabilities) > 0:
print("""\"capabilities\": """ + json.dumps(capabilities) + ",")
else:
print("")
EOF
NESTED_CAPABILITIES_STRING="$(cat $MULTUS_AUTOCONF_DIR/$MASTER_PLUGIN | \
python $capabilities_python_filter_tmpfile)"
rm $capabilities_python_filter_tmpfile
log "Nested capabilities string: $NESTED_CAPABILITIES_STRING"
MASTER_PLUGIN_LOCATION=$MULTUS_AUTOCONF_DIR/$MASTER_PLUGIN
MASTER_PLUGIN_JSON="$(cat $MASTER_PLUGIN_LOCATION)"
log "Using $MASTER_PLUGIN_LOCATION as a source to generate the Multus configuration"
CONF=$(cat <<-EOF
{
$CNI_VERSION_STRING
"name": "$MASTER_PLUGIN_NET_NAME",
"type": "multus",
$NESTED_CAPABILITIES_STRING
$ISOLATION_STRING
$GLOBAL_NAMESPACES_STRING
$LOG_TO_STDERR_STRING
$LOG_LEVEL_STRING
$LOG_FILE_STRING
$ADDITIONAL_BIN_DIR_STRING
$READINESS_INDICATOR_FILE_STRING
"kubeconfig": "$MULTUS_KUBECONFIG_FILE_HOST",
"delegates": [
$MASTER_PLUGIN_JSON
]
}
EOF
)
tmpfile=$(mktemp)
echo $CONF > $tmpfile
mv $tmpfile $CNI_CONF_DIR/00-multus.conf
log "Config file created @ $CNI_CONF_DIR/00-multus.conf"
echo $CONF
# If we're not performing the cleanup on exit, we can safely rename the config file.
if [ "$RENAME_SOURCE_CONFIG_FILE" == true ]; then
mv ${MULTUS_AUTOCONF_DIR}/${MASTER_PLUGIN} ${MULTUS_AUTOCONF_DIR}/${MASTER_PLUGIN}.old
log "Original master file moved to ${MULTUS_AUTOCONF_DIR}/${MASTER_PLUGIN}.old"
fi
if [ "$RESTART_CRIO" == true ]; then
# Restart CRIO only once.
if [ "$CRIO_RESTARTED_ONCE" == false ]; then
log "Restarting crio"
systemctl restart crio
CRIO_RESTARTED_ONCE=true
fi
fi
fi
done
fi
}
generateMultusConf
# ---------------------- end Generate "00-multus.conf".
# Enter either sleep loop, or watch loop...
if [ "$MULTUS_CLEANUP_CONFIG_ON_EXIT" == true ]; then
log "Entering watch loop..."
while true; do
# Check and see if the original master plugin configuration exists...
if [ ! -f "$MASTER_PLUGIN_LOCATION" ]; then
log "Master plugin @ $MASTER_PLUGIN_LOCATION has been deleted. Allowing 45 seconds for its restoration..."
sleep 10
for i in {1..35}
do
if [ -f "$MASTER_PLUGIN_LOCATION" ]; then
log "Master plugin @ $MASTER_PLUGIN_LOCATION was restored. Regenerating given configuration."
break
fi
sleep 1
done
generateMultusConf
log "Continuing watch loop after configuration regeneration..."
fi
sleep 1
done
else
log "Entering sleep (success)..."
if tty -s; then
read
else
sleep infinity
fi
fi

View File

@@ -1,179 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: network-attachment-definitions.k8s.cni.cncf.io
spec:
group: k8s.cni.cncf.io
scope: Namespaced
names:
plural: network-attachment-definitions
singular: network-attachment-definition
kind: NetworkAttachmentDefinition
shortNames:
- net-attach-def
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
config:
type: string
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus
rules:
- apiGroups: ["k8s.cni.cncf.io"]
resources:
- '*'
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- pods/status
verbs:
- get
- update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: multus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: multus
subjects:
- kind: ServiceAccount
name: multus
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: multus
namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
name: multus-cni-config
namespace: kube-system
labels:
tier: node
app: multus
data:
# NOTE: If you'd prefer to manually apply a configuration file, you may create one here.
# In the case you'd like to customize the Multus installation, you should change the arguments to the Multus pod
# change the "args" line below from
# - "--multus-conf-file=auto"
# to:
# "--multus-conf-file=/tmp/multus-conf/70-multus.conf"
# Additionally -- you should ensure that the name "70-multus.conf" is the alphabetically first name in the
# /etc/cni/net.d/ directory on each node, otherwise, it will not be used by the Kubelet.
cni-conf.json: |
{
"name": "multus-cni-network",
"type": "multus",
"capabilities": {
"portMappings": true
},
"delegates": [
{
"cniVersion": "0.3.1",
"name": "default-cni-network",
"plugins": [
{
"type": "flannel",
"name": "flannel.1",
"delegate": {
"isDefaultGateway": true,
"hairpinMode": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
],
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig"
}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-multus-ds
namespace: kube-system
labels:
tier: node
app: multus
name: multus
spec:
selector:
matchLabels:
name: multus
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
tier: node
app: multus
name: multus
spec:
hostNetwork: true
tolerations:
- operator: Exists
effect: NoSchedule
serviceAccountName: multus
containers:
- name: kube-multus
image: ghcr.io/k8snetworkplumbingwg/multus-cni:stable
command: ["/entrypoint.sh"]
args:
- "--multus-conf-file=auto"
- "--cni-version=0.3.1"
- "--cni-bin-dir=/host/home/kubernetes/bin"
resources:
requests:
cpu: "100m"
memory: "50Mi"
limits:
cpu: "100m"
memory: "50Mi"
securityContext:
privileged: true
volumeMounts:
- name: cni
mountPath: /host/etc/cni/net.d
- name: cnibin
mountPath: /host/home/kubernetes/bin
- name: multus-cfg
mountPath: /tmp/multus-conf
volumes:
- name: cni
hostPath:
path: /etc/cni/net.d
- name: cnibin
hostPath:
path: /home/kubernetes/bin
- name: multus-cfg
configMap:
name: multus-cni-config
items:
- key: cni-conf.json
path: 70-multus.conf

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,16 +12,15 @@
// 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 checkpoint
import (
"encoding/json"
"io/ioutil"
"os"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
v1 "k8s.io/api/core/v1"
)
@@ -33,7 +33,7 @@ type PodDevicesEntry struct {
PodUID string
ContainerName string
ResourceName string
DeviceIDs []string
DeviceIDs map[int64][]string
AllocResp []byte
}
@@ -72,7 +72,7 @@ func getCheckpoint(filePath string) (types.ResourceClient, error) {
func (cp *checkpoint) getPodEntries() error {
cpd := &checkpointFileData{}
rawBytes, err := ioutil.ReadFile(cp.fileName)
rawBytes, err := os.ReadFile(cp.fileName)
if err != nil {
return logging.Errorf("getPodEntries: error reading file %s\n%v\n", checkPointfile, err)
}
@@ -86,7 +86,7 @@ func (cp *checkpoint) getPodEntries() error {
return nil
}
// GetComputeDeviceMap returns an instance of a map of ResourceInfo
// GetPodResourceMap returns an instance of a map of ResourceInfo
func (cp *checkpoint) GetPodResourceMap(pod *v1.Pod) (map[string]*types.ResourceInfo, error) {
podID := string(pod.UID)
resourceMap := make(map[string]*types.ResourceInfo)
@@ -97,12 +97,14 @@ func (cp *checkpoint) GetPodResourceMap(pod *v1.Pod) (map[string]*types.Resource
for _, pod := range cp.podEntires {
if pod.PodUID == podID {
entry, ok := resourceMap[pod.ResourceName]
if ok {
// already exists; append to it
entry.DeviceIDs = append(entry.DeviceIDs, pod.DeviceIDs...)
} else {
if !ok {
// new entry
resourceMap[pod.ResourceName] = &types.ResourceInfo{DeviceIDs: pod.DeviceIDs}
entry = &types.ResourceInfo{}
resourceMap[pod.ResourceName] = entry
}
for _, v := range pod.DeviceIDs {
// already exists; append to it
entry.DeviceIDs = append(entry.DeviceIDs, v...)
}
}
}

View File

@@ -1,16 +1,32 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2021 Multus 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 checkpoint
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"fmt"
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"io/ioutil"
"testing"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sTypes "k8s.io/apimachinery/pkg/types"
@@ -25,7 +41,7 @@ type fakeCheckpoint struct {
}
func (fc *fakeCheckpoint) WriteToFile(inBytes []byte) error {
return ioutil.WriteFile(fc.fileName, inBytes, 0600)
return os.WriteFile(fc.fileName, inBytes, 0600)
}
func (fc *fakeCheckpoint) DeleteFile() error {
@@ -45,10 +61,11 @@ var _ = BeforeSuite(func() {
"PodUID": "970a395d-bb3b-11e8-89df-408d5c537d23",
"ContainerName": "appcntr1",
"ResourceName": "intel.com/sriov_net_A",
"DeviceIDs": [
"0000:03:02.3",
"0000:03:02.0"
],
"DeviceIDs": {"-1": [
"0000:03:02.3",
"0000:03:02.0"
]
},
"AllocResp": "CikKC3NyaW92X25ldF9BEhogMDAwMDowMzowMi4zIDAwMDA6MDM6MDIuMA=="
}
],
@@ -143,10 +160,10 @@ var _ = Describe("Kubelet checkpoint data read operations", func() {
"PodUID": "970a395d-bb3b-11e8-89df-408d5c537d23",
"ContainerName": "appcntr1",
"ResourceName": "intel.com/sriov_net_A",
"DeviceIDs": [
"DeviceIDs": { "-1": [
"0000:03:02.3",
"0000:03:02.0"
],
] },
"AllocResp": "CikKC3NyaW92X25ldF9BEhogMDAwMDowMzowMi4zIDAwMDA6MDM6MDIuMA=="
}
],

17
pkg/checkpoint/doc.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Multus 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 checkpoint is the package that contains the libraries that manipulates kubelet's
// checkpoint API
package checkpoint

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2023 Multus 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 cmdutils is the package that contains utilities for multus command
package cmdutils
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"testing"
)
func TestServer(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "cmdutils")
}

84
pkg/cmdutils/utils.go Normal file
View File

@@ -0,0 +1,84 @@
// Copyright (c) 2023 Multus 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 cmdutils is the package that contains utilities for multus command
package cmdutils
import (
"fmt"
"io"
"os"
"path/filepath"
)
// CopyFileAtomic does file copy atomically
func CopyFileAtomic(srcFilePath, destDir, tempFileName, destFileName string) error {
tempFilePath := filepath.Join(destDir, tempFileName)
// check temp filepath and remove old file if exists
if _, err := os.Stat(tempFilePath); err == nil {
err = os.Remove(tempFilePath)
if err != nil {
return fmt.Errorf("cannot remove old temp file %q: %v", tempFilePath, err)
}
}
// create temp file
f, err := os.CreateTemp(destDir, tempFileName)
defer f.Close()
if err != nil {
return fmt.Errorf("cannot create temp file %q in %q: %v", tempFileName, destDir, err)
}
srcFile, err := os.Open(srcFilePath)
if err != nil {
return fmt.Errorf("cannot open file %q: %v", srcFilePath, err)
}
defer srcFile.Close()
// Copy file to tempfile
_, err = io.Copy(f, srcFile)
if err != nil {
f.Close()
os.Remove(tempFilePath)
return fmt.Errorf("cannot write data to temp file %q: %v", tempFilePath, err)
}
if err := f.Sync(); err != nil {
return fmt.Errorf("cannot flush temp file %q: %v", tempFilePath, err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("cannot close temp file %q: %v", tempFilePath, err)
}
// change file mode if different
destFilePath := filepath.Join(destDir, destFileName)
_, err = os.Stat(destFilePath)
if err != nil && !os.IsNotExist(err) {
return err
}
srcFileStat, err := os.Stat(srcFilePath)
if err != nil {
return err
}
if err := os.Chmod(f.Name(), srcFileStat.Mode()); err != nil {
return fmt.Errorf("cannot set stat on temp file %q: %v", f.Name(), err)
}
// replace file with tempfile
if err := os.Rename(f.Name(), destFilePath); err != nil {
return fmt.Errorf("cannot replace %q with temp file %q: %v", destFilePath, tempFilePath, err)
}
return nil
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2023 Multus 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 cmdutils is the package that contains utilities for multus command
package cmdutils
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"fmt"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("thin entrypoint testing", func() {
It("Run CopyFileAtomic()", func() {
// create directory and files
tmpDir, err := os.MkdirTemp("", "multus_thin_entrypoint_tmp")
Expect(err).NotTo(HaveOccurred())
// create source directory
srcDir := fmt.Sprintf("%s/src", tmpDir)
err = os.Mkdir(srcDir, 0755)
Expect(err).NotTo(HaveOccurred())
// create destination directory
destDir := fmt.Sprintf("%s/dest", tmpDir)
err = os.Mkdir(destDir, 0755)
Expect(err).NotTo(HaveOccurred())
// sample source file
srcFilePath := fmt.Sprintf("%s/sampleInput", srcDir)
err = os.WriteFile(srcFilePath, []byte("sampleInputABC"), 0744)
Expect(err).NotTo(HaveOccurred())
// old files in dest
destFileName := "sampleInputDest"
destFilePath := fmt.Sprintf("%s/%s", destDir, destFileName)
err = os.WriteFile(destFilePath, []byte("inputOldXYZ"), 0611)
Expect(err).NotTo(HaveOccurred())
tempFileName := "temp_file"
err = CopyFileAtomic(srcFilePath, destDir, tempFileName, destFileName)
Expect(err).NotTo(HaveOccurred())
// check file mode
stat, err := os.Stat(destFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(stat.Mode()).To(Equal(os.FileMode(0744)))
// check file contents
destFileByte, err := os.ReadFile(destFilePath)
Expect(err).NotTo(HaveOccurred())
Expect(destFileByte).To(Equal([]byte("sampleInputABC")))
err = os.RemoveAll(tmpDir)
Expect(err).NotTo(HaveOccurred())
})
})

16
pkg/k8sclient/doc.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2022 Multus 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 k8sclient is the package that contains the Kubernetes client libraries.
package k8sclient

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2017 Intel Corporation
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -22,28 +23,26 @@ import (
"os"
"regexp"
"strings"
"time"
"syscall"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/klog"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
netclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/typed/k8s.cni.cncf.io/v1"
netclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned"
netlister "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/listers/k8s.cni.cncf.io/v1"
netutils "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/kubeletclient"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/kubeletclient"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
)
const (
@@ -60,9 +59,13 @@ type NoK8sNetworkError struct {
// ClientInfo contains information given from k8s client
type ClientInfo struct {
Client kubernetes.Interface
NetClient netclient.K8sCniCncfIoV1Interface
NetClient netclient.Interface
EventBroadcaster record.EventBroadcaster
EventRecorder record.EventRecorder
// multus-thick uses these informer
PodInformer cache.SharedIndexInformer
NetDefInformer cache.SharedIndexInformer
}
// AddPod adds pod into kubernetes
@@ -72,6 +75,10 @@ func (c *ClientInfo) AddPod(pod *v1.Pod) (*v1.Pod, error) {
// GetPod gets pod from kubernetes
func (c *ClientInfo) GetPod(namespace, name string) (*v1.Pod, error) {
if c.PodInformer != nil {
logging.Debugf("GetPod for [%s/%s] will use informer cache", namespace, name)
return listers.NewPodLister(c.PodInformer.GetIndexer()).Pods(namespace).Get(name)
}
return c.Client.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}
@@ -82,7 +89,16 @@ func (c *ClientInfo) DeletePod(namespace, name string) error {
// AddNetAttachDef adds net-attach-def into kubernetes
func (c *ClientInfo) AddNetAttachDef(netattach *nettypes.NetworkAttachmentDefinition) (*nettypes.NetworkAttachmentDefinition, error) {
return c.NetClient.NetworkAttachmentDefinitions(netattach.ObjectMeta.Namespace).Create(context.TODO(), netattach, metav1.CreateOptions{})
return c.NetClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(netattach.ObjectMeta.Namespace).Create(context.TODO(), netattach, metav1.CreateOptions{})
}
// GetNetAttachDef get net-attach-def from kubernetes
func (c *ClientInfo) GetNetAttachDef(namespace, name string) (*nettypes.NetworkAttachmentDefinition, error) {
if c.NetDefInformer != nil {
logging.Debugf("GetNetAttachDef for [%s/%s] will use informer cache", namespace, name)
return netlister.NewNetworkAttachmentDefinitionLister(c.NetDefInformer.GetIndexer()).NetworkAttachmentDefinitions(namespace).Get(name)
}
return c.NetClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}
// Eventf puts event into kubernetes events
@@ -92,37 +108,48 @@ func (c *ClientInfo) Eventf(object runtime.Object, eventtype, reason, messageFmt
}
}
func (e *NoK8sNetworkError) Error() string { return string(e.message) }
func (e *NoK8sNetworkError) Error() string { return e.message }
// SetNetworkStatus sets network status into Pod annotation
func SetNetworkStatus(client *ClientInfo, k8sArgs *types.K8sArgs, netStatus []nettypes.NetworkStatus, conf *types.NetConf) error {
podName := string(k8sArgs.K8S_POD_NAME)
podNamespace := string(k8sArgs.K8S_POD_NAMESPACE)
podUID := string(k8sArgs.K8S_POD_UID)
return SetPodNetworkStatusAnnotation(client, podName, podNamespace, podUID, netStatus, conf)
}
// SetPodNetworkStatusAnnotation sets network status into Pod annotation
func SetPodNetworkStatusAnnotation(client *ClientInfo, podName string, podNamespace string, podUID string, netStatus []nettypes.NetworkStatus, conf *types.NetConf) error {
var err error
logging.Debugf("SetNetworkStatus: %v, %v, %v, %v", client, k8sArgs, netStatus, conf)
logging.Debugf("SetPodNetworkStatusAnnotation: %v, %v, %v", client, netStatus, conf)
client, err = GetK8sClient(conf.Kubeconfig, client)
if err != nil {
return logging.Errorf("SetNetworkStatus: %v", err)
}
if client == nil || client.Client == nil {
if client == nil {
if len(conf.Delegates) == 0 {
// No available kube client and no delegates, we can't do anything
return logging.Errorf("SetNetworkStatus: must have either Kubernetes config or delegates")
}
logging.Debugf("SetNetworkStatus: kube client info is not defined, skip network status setup")
logging.Debugf("SetPodNetworkStatusAnnotation: kube client info is not defined, skip network status setup")
return nil
}
podName := string(k8sArgs.K8S_POD_NAME)
podNamespace := string(k8sArgs.K8S_POD_NAMESPACE)
pod, err := client.GetPod(podNamespace, podName)
if err != nil {
return logging.Errorf("SetNetworkStatus: failed to query the pod %v in out of cluster comm: %v", podName, err)
return logging.Errorf("SetPodNetworkStatusAnnotation: failed to query the pod %v in out of cluster comm: %v", podName, err)
}
if podUID != "" && string(pod.UID) != podUID && !IsStaticPod(pod) {
return logging.Errorf("SetNetworkStatus: expected pod %s/%s UID %q but got %q from Kube API", podNamespace, podName, podUID, pod.UID)
}
if netStatus != nil {
err = netutils.SetNetworkStatus(client.Client, pod, netStatus)
if err != nil {
return logging.Errorf("SetNetworkStatus: failed to update the pod %v in out of cluster comm: %v", podName, err)
return logging.Errorf("SetPodNetworkStatusAnnotation: failed to update the pod %v in out of cluster comm: %v", podName, err)
}
}
@@ -156,16 +183,22 @@ func parsePodNetworkObjectName(podnetwork string) (string, string, string, error
// Check and see if each item matches the specification for valid attachment name.
// "Valid attachment names must be comprised of units of the DNS-1123 label format"
// [a-z0-9]([-a-z0-9]*[a-z0-9])?
// And we allow at (@), and forward slash (/) (units separated by commas)
// It must start and end alphanumerically.
allItems := []string{netNsName, networkName, netIfName}
allItems := []string{netNsName, networkName}
expr := regexp.MustCompile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
for i := range allItems {
matched, _ := regexp.MatchString("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", allItems[i])
matched := expr.MatchString(allItems[i])
if !matched && len([]rune(allItems[i])) > 0 {
return "", "", "", logging.Errorf(fmt.Sprintf("parsePodNetworkObjectName: Failed to parse: one or more items did not match comma-delimited format (must consist of lower case alphanumeric characters). Must start and end with an alphanumeric character), mismatch @ '%v'", allItems[i]))
}
}
if len(netIfName) > 0 {
if len(netIfName) > (syscall.IFNAMSIZ-1) || strings.ContainsAny(netIfName, " \t\n\v\f\r/") {
return "", "", "", logging.Errorf(fmt.Sprintf("parsePodNetworkObjectName: Failed to parse interface name: must be less than 15 chars and not contain '/' or spaces. interface name '%s'", netIfName))
}
}
logging.Debugf("parsePodNetworkObjectName: parsed: %s, %s, %s", netNsName, networkName, netIfName)
return netNsName, networkName, netIfName, nil
}
@@ -178,7 +211,7 @@ func parsePodNetworkAnnotation(podNetworks, defaultNamespace string) ([]*types.N
return nil, logging.Errorf("parsePodNetworkAnnotation: pod annotation does not have \"network\" as key")
}
if strings.IndexAny(podNetworks, "[{\"") >= 0 {
if strings.ContainsAny(podNetworks, "[{\"") {
if err := json.Unmarshal([]byte(podNetworks), &networks); err != nil {
return nil, logging.Errorf("parsePodNetworkAnnotation: failed to parse pod Network Attachment Selection Annotation JSON format: %v", err)
}
@@ -242,7 +275,8 @@ func parsePodNetworkAnnotation(podNetworks, defaultNamespace string) ([]*types.N
func getKubernetesDelegate(client *ClientInfo, net *types.NetworkSelectionElement, confdir string, pod *v1.Pod, resourceMap map[string]*types.ResourceInfo) (*types.DelegateNetConf, map[string]*types.ResourceInfo, error) {
logging.Debugf("getKubernetesDelegate: %v, %v, %s, %v, %v", client, net, confdir, pod, resourceMap)
customResource, err := client.NetClient.NetworkAttachmentDefinitions(net.Namespace).Get(context.TODO(), net.Name, metav1.GetOptions{})
customResource, err := client.GetNetAttachDef(net.Namespace, net.Name)
if err != nil {
errMsg := fmt.Sprintf("cannot find a network-attachment-definition (%s) in namespace (%s): %v", net.Name, net.Namespace, err)
if client != nil {
@@ -360,11 +394,14 @@ func TryLoadPodDelegates(pod *v1.Pod, conf *types.NetConf, clientInfo *ClientInf
}
}
if isGatewayConfigured == true {
types.CheckGatewayConfig(conf.Delegates)
if isGatewayConfigured {
err = types.CheckGatewayConfig(conf.Delegates)
if err != nil {
return 0, nil, err
}
}
return len(delegates), clientInfo, nil
return len(delegates), clientInfo, err
}
if _, ok := err.(*NoK8sNetworkError); ok {
@@ -373,66 +410,6 @@ func TryLoadPodDelegates(pod *v1.Pod, conf *types.NetConf, clientInfo *ClientInf
return 0, clientInfo, err
}
// GetK8sClient gets client info from kubeconfig
func GetK8sClient(kubeconfig string, kubeClient *ClientInfo) (*ClientInfo, error) {
logging.Debugf("GetK8sClient: %s, %v", kubeconfig, kubeClient)
// If we get a valid kubeClient (eg from testcases) just return that
// one.
if kubeClient != nil {
return kubeClient, nil
}
var err error
var config *rest.Config
// Otherwise try to create a kubeClient from a given kubeConfig
if kubeconfig != "" {
// uses the current context in kubeconfig
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, logging.Errorf("GetK8sClient: failed to get context for the kubeconfig %v: %v", kubeconfig, err)
}
} else if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
// Try in-cluster config where multus might be running in a kubernetes pod
config, err = rest.InClusterConfig()
if err != nil {
return nil, logging.Errorf("GetK8sClient: failed to get context for in-cluster kube config: %v", err)
}
} else {
// No kubernetes config; assume we shouldn't talk to Kube at all
return nil, nil
}
// Specify that we use gRPC
config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
config.ContentType = "application/vnd.kubernetes.protobuf"
// Set the config timeout to one minute.
config.Timeout = time.Minute
// creates the clientset
client, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
netclient, err := netclient.NewForConfig(config)
if err != nil {
return nil, err
}
broadcaster := record.NewBroadcaster()
broadcaster.StartLogging(klog.Infof)
broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "multus"})
return &ClientInfo{
Client: client,
NetClient: netclient,
EventBroadcaster: broadcaster,
EventRecorder: recorder,
}, nil
}
// GetPodNetwork gets net-attach-def annotation from pod
func GetPodNetwork(pod *v1.Pod) ([]*types.NetworkSelectionElement, error) {
logging.Debugf("GetPodNetwork: %v", pod)
@@ -492,32 +469,41 @@ func isValidNamespaceReference(targetns string, allowednamespaces []string) bool
return false
}
// getNetDelegate loads delegate network for clusterNetwork/defaultNetworks
func getNetDelegate(client *ClientInfo, pod *v1.Pod, netname, confdir, namespace string, resourceMap map[string]*types.ResourceInfo) (*types.DelegateNetConf, map[string]*types.ResourceInfo, error) {
logging.Debugf("getNetDelegate: %v, %v, %v, %s", client, netname, confdir, namespace)
// option1) search CRD object for the network
net := &types.NetworkSelectionElement{
Name: netname,
Namespace: namespace,
}
delegate, resourceMap, err := getKubernetesDelegate(client, net, confdir, pod, resourceMap)
if err == nil {
return delegate, resourceMap, nil
}
// option2) search CNI json config file
var configBytes []byte
configBytes, err = netutils.GetCNIConfigFromFile(netname, confdir)
if err == nil {
delegate, err := types.LoadDelegateNetConf(configBytes, nil, "", "")
isNetnamePath := strings.Contains(netname, "/")
// if netname is not directory or file, it must be net-attach-def name or CNI config name
if !isNetnamePath {
// option1) search CRD object for the network
net := &types.NetworkSelectionElement{
Name: netname,
Namespace: namespace,
}
delegate, resourceMap, err := getKubernetesDelegate(client, net, confdir, pod, resourceMap)
if err == nil {
return delegate, resourceMap, nil
}
// option2) search CNI json config file, which has <netname> as CNI name, from confDir
configBytes, err = netutils.GetCNIConfigFromFile(netname, confdir)
if err == nil {
delegate, err := types.LoadDelegateNetConf(configBytes, nil, "", "")
if err != nil {
return nil, resourceMap, err
}
return delegate, resourceMap, nil
}
} else {
fInfo, err := os.Stat(netname)
if err != nil {
return nil, resourceMap, err
}
return delegate, resourceMap, nil
}
// option3) search directory
fInfo, err := os.Stat(netname)
if err == nil {
// option3) search directory
if fInfo.IsDir() {
files, err := libcni.ConfFiles(netname, []string{".conf", ".conflist"})
if err != nil {
@@ -535,6 +521,29 @@ func getNetDelegate(client *ClientInfo, pod *v1.Pod, netname, confdir, namespace
}
return nil, resourceMap, err
}
} else {
// option4) if file path (absolute), then load it directly
if strings.HasSuffix(netname, ".conflist") {
confList, err := libcni.ConfListFromFile(netname)
if err != nil {
return nil, resourceMap, logging.Errorf("error loading CNI conflist file %s: %v", netname, err)
}
configBytes = confList.Bytes
} else {
conf, err := libcni.ConfFromFile(netname)
if err != nil {
return nil, resourceMap, logging.Errorf("error loading CNI config file %s: %v", netname, err)
}
if conf.Network.Type == "" {
return nil, resourceMap, logging.Errorf("error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", netname)
}
configBytes = conf.Bytes
}
delegate, err := types.LoadDelegateNetConf(configBytes, nil, "", "")
if err != nil {
return nil, resourceMap, err
}
return delegate, resourceMap, nil
}
}
return nil, resourceMap, logging.Errorf("getNetDelegate: cannot find network: %v", netname)
@@ -611,3 +620,16 @@ func tryLoadK8sPodDefaultNetwork(kubeClient *ClientInfo, pod *v1.Pod, conf *type
return delegate, nil
}
// ConfigSourceAnnotationKey specifies kubernetes annotation, defined in k8s.io/kubernetes/pkg/kubelet/types
const ConfigSourceAnnotationKey = "kubernetes.io/config.source"
// IsStaticPod returns true if the pod is static pod.
func IsStaticPod(pod *v1.Pod) bool {
if pod.Annotations != nil {
if source, ok := pod.Annotations[ConfigSourceAnnotationKey]; ok {
return source != "api"
}
}
return false
}

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2017 Intel Corporation
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,22 +12,22 @@
// 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 k8sclient
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
types020 "github.com/containernetworking/cni/pkg/types/020"
testutils "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/testing"
testutils "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/testing"
"github.com/containernetworking/cni/pkg/skel"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
netfake "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/fake"
@@ -34,7 +35,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -47,7 +48,7 @@ func TestK8sClient(t *testing.T) {
func NewFakeClientInfo() *ClientInfo {
return &ClientInfo{
Client: fake.NewSimpleClientset(),
NetClient: netfake.NewSimpleClientset().K8sCniCncfIoV1(),
NetClient: netfake.NewSimpleClientset(),
}
}
@@ -55,9 +56,12 @@ var _ = Describe("k8sclient operations", func() {
var tmpDir string
var err error
var genericConf string
var args *skel.CmdArgs
const fakePodName string = "testPod"
BeforeEach(func() {
tmpDir, err = ioutil.TempDir("", "multus_tmp")
tmpDir, err = os.MkdirTemp("", "multus_tmp")
Expect(err).NotTo(HaveOccurred())
genericConf = `{
"name":"node-cni-network",
@@ -69,6 +73,11 @@ var _ = Describe("k8sclient operations", func() {
}],
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`
args = &skel.CmdArgs{
// Values come from NewFakePod()
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s;K8S_POD_UID=%s", fakePodName, "test", "testUID"),
}
})
AfterEach(func() {
@@ -77,7 +86,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves delegates from kubernetes using simple format annotation", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
fakePod := testutils.NewFakePod(fakePodName, "net1,net2", "")
net1 := `{
"name": "net1",
"type": "mynet",
@@ -94,10 +103,6 @@ var _ = Describe("k8sclient operations", func() {
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -130,15 +135,12 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails when the network does not exist", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
fakePod := testutils.NewFakePod(fakePodName, "net1,net2", "")
net3 := `{
"name": "net3",
"type": "mynet3",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -160,7 +162,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves delegates from kubernetes using JSON format annotation", func() {
fakePod := testutils.NewFakePod("testpod", `[
fakePod := testutils.NewFakePod(fakePodName, `[
{"name":"net1"},
{
"name":"net2",
@@ -172,9 +174,6 @@ var _ = Describe("k8sclient operations", func() {
"namespace":"other-ns"
}
]`, "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -218,10 +217,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails when the JSON format annotation is invalid", func() {
fakePod := testutils.NewFakePod("testpod", "[adsfasdfasdfasf]", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fakePod := testutils.NewFakePod(fakePodName, "[adsfasdfasdfasf]", "")
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -236,7 +232,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("can set the default-gateway on an additional interface", func() {
fakePod := testutils.NewFakePod("testpod", `[
fakePod := testutils.NewFakePod(fakePodName, `[
{"name":"net1"},
{
"name":"net2",
@@ -247,9 +243,6 @@ var _ = Describe("k8sclient operations", func() {
"namespace":"other-ns"
}
]`, "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -294,10 +287,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves delegates from kubernetes using on-disk config files", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fakePod := testutils.NewFakePod(fakePodName, "net1,net2", "")
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -338,10 +328,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("injects network name into minimal thick plugin CNI config", func() {
fakePod := testutils.NewFakePod("testpod", "net1", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fakePod := testutils.NewFakePod(fakePodName, "net1", "")
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -365,10 +352,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails when on-disk config file is not valid", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
fakePod := testutils.NewFakePod(fakePodName, "net1,net2", "")
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -399,7 +383,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves cluster network from CRD", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
fakePod := testutils.NewFakePod(fakePodName, "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -409,10 +393,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -431,7 +411,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves default networks from CRD", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
fakePod := testutils.NewFakePod(fakePodName, "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -442,10 +422,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -469,7 +445,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("ignore default networks from CRD in case of kube-system namespace", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
fakePod := testutils.NewFakePod(fakePodName, "", "")
// overwrite namespace
fakePod.ObjectMeta.Namespace = "kube-system"
conf := `{
@@ -482,10 +458,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -507,7 +479,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("retrieves cluster network from file", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
fakePod := testutils.NewFakePod(fakePodName, "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -518,10 +490,6 @@ var _ = Describe("k8sclient operations", func() {
netConf.ConfDir = tmpDir
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -545,8 +513,8 @@ var _ = Describe("k8sclient operations", func() {
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("retrieves cluster network from path", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
It("retrieves cluster network from directory path", func() {
fakePod := testutils.NewFakePod(fakePodName, "", "")
conf := fmt.Sprintf(`{
"name":"node-cni-network",
"type":"multus",
@@ -556,10 +524,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -581,8 +545,39 @@ var _ = Describe("k8sclient operations", func() {
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("retrieves cluster network from cni config path", func() {
net1Name := filepath.Join(tmpDir, "10-net1.conf")
os.WriteFile(net1Name, []byte(`{
"name": "net1",
"type": "mynet",
"cniVersion": "0.3.1"
}`), 0600)
fakePod := testutils.NewFakePod(fakePodName, "", "")
conf := fmt.Sprintf(`{
"name":"node-cni-network",
"type":"multus",
"clusterNetwork": "%s",
"kubeconfig":"/etc/kubernetes/node-kubeconfig.yaml"
}`, net1Name)
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
_, err = GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
_, err = GetDefaultNetworks(fakePod, netConf, clientInfo, nil)
Expect(err).NotTo(HaveOccurred())
Expect(len(netConf.Delegates)).To(Equal(1))
Expect(netConf.Delegates[0].Conf.Name).To(Equal("net1"))
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet"))
})
It("Error in case of CRD not found", func() {
fakePod := testutils.NewFakePod("testpod", "", "")
fakePod := testutils.NewFakePod(fakePodName, "", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -592,10 +587,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -608,7 +599,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("overwrite cluster network when Pod annotation is set", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -619,10 +610,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -650,7 +637,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails with bad confdir", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -661,10 +648,6 @@ var _ = Describe("k8sclient operations", func() {
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -686,7 +669,7 @@ var _ = Describe("k8sclient operations", func() {
It("overwrite multus config when Pod annotation is set", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -701,10 +684,6 @@ var _ = Describe("k8sclient operations", func() {
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet2"))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -723,7 +702,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails with no kubeclient and invalid kubeconfig", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -738,10 +717,6 @@ var _ = Describe("k8sclient operations", func() {
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet2"))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -757,7 +732,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("fails with no kubeclient and no kubeconfig", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -772,10 +747,6 @@ var _ = Describe("k8sclient operations", func() {
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet2"))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -795,7 +766,7 @@ var _ = Describe("k8sclient operations", func() {
})
It("uses cached delegates when an error in loading from pod annotation occurs", func() {
dir, err := ioutil.TempDir("", "multus-test")
dir, err := os.MkdirTemp("", "multus-test")
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(dir) // clean up
@@ -821,7 +792,7 @@ users:
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBMUxyZmUxaXNQaTNDYng4eHRDWG1QTlE2L0xXYVVEa1ZRNVBkdGtQK3dVYmdJUFJ6CmNtRzNEcnUya0kvamsxMGQzaUgzOVZlZDlHVjczbnZyVm5Id0pzZEF5QzVmYlRTQVBUVVNJOW90MVFGbENRUGcKQ2JLbjBRbmxUeUhXMmNnTis2Q3BWK0U4dFpWajJ2cDZoTDlmR0s2bUJCL2I4VFN3RmYxRmtnWC9hTGN1emZDaApmVFNDOWlRTk15cEFjamZadEkzWiszbHVzSVB6TVpBNTZPZXNodzlnRkJHUkw3RzZHWmZKcG9OaGVxTDlmandUClRkNURlbVZXcUxUR05ZNWhhV3hicy9VbThuM0QvdlV1QkVPYTYxQ2cvcGljUkU3Um14SmFJRWJiQlNTV1dkSDMKWDlrem5RdHJrUXloL28xZnpSV1pxeWFCY3hxR1FWN0JSSzFtSndJREFRQUJBb0lCQUJ0bjA4QzFSTU5oNjhtYgpFREV3TE1BcmEwb0JMMWNrYzN2WVFkam9XNXFVd2UwYzhQNk1YaVAweE9sTTBEbTg1a3NtdnlZSldwMFFzZXVRCnRWbldwZVNwQ015QlJPUHh2bytrRmFrdXczYk1qaktpSUN1L3EyVC96RjNzY3h4dGJIZTlVL094WGJ2YStobE0KNlpuT2ViYlpVU1A0NHNIcFVzSVNkZk1BK00ySmg1UFJibGZWaUFEY1hxNFR5RU1JaStzRkhOcFIrdmdWZzRFawp4RmFVaS83V0E2YUxWVzBUTzREdjMwbTJ0TVczWXN1bk1LTU0xOTNyUEZrU0dEdFpheWV2Z0JDeURXaFhOTEo2Clh1cTNxSUg4bFE2bzRBUjMvcDc1ZW9hOCtrVzVmT3o2UWF3WnpPYlBENkRCQlVOYVM1YklXaVV1dmx5L0JlM20KZnlxK3NRRUNnWUVBMW84R3l6ODk2bFhwdU1yVXVsb2orbGp5U0FaNkpLOCsyaFMvQnpyREx6NlpvY3FzKzg3awpVUkwzKy9LL1pja2pIMVVDeXROVVZ6Q3RKaG4zZmdLS2dpQWhsU0pNRzhqc05sbEkydFZSazNZZ1RCcUg2bXZxCit3citsTUxoUDZxbWFObUx3QXljY2lEanpMdXlRdjhVOFhKazJOdVFsQlFwbkt2eWJIRGdxSUVDZ1lFQS9kRnMKazNlYmRNNFAxV2psYXJoRTV5blpuRmdQbDg1L2Vudk4rQ1oxcStlMGxYendaUGswdWdJUWozYyt2UEpLWlh0OApLWk1HQjM0N2VLNlFIL3J1a2xRWXlLOStHeUV1YnRJQUZ2NWFrYXZxV1haR1p5ZC9QdDR1V09adXMrd3BnSG00CkxFY0lzZElsYkpFY2RJTzJyb3FaY0VNY3FEbGtXcTdwQWxqU2VxY0NnWUJYdUQ0RTFxUlByRFJVRXNrS0wxUksKUkJjNkR6dmN4N0VncEI2OXErNms0Q2tibHF0R2YvMmtqK2JISVNYVFRYcUlrczhEY1ljbjVvVEQ4UlhZZE4xLworZmNBNi9iRjNVMkZvdGRBY0xwYldZNDJ6eG9HWTN5OGluQXZEY1hkcTcxQlhML2dFc2ZiZVVycEowdm9URFdaCnlUVWwzQTZ1RzlndmI3VTdWS0xsQVFLQmdBTmNscmVOU2YzT0ROK2l1QWNsMGFQT0poZXdBdVRiMDB4bi8xNWUKQkFqMjFLbDJNaWprTkJLU25HMktBc2ExM3M1aFNFKzBwc3ZLbkRjSStOZXpseDFSQjlNQW9BYno5WTE2TW80YgphRSt0bXpqOEhBcVp0MUc1MTV0TjBnR0lDelNzYUFnT0dNdGlJU1RDOTBHRHpST2F1bFdHVGdiY1c3dm52U1pPCnp0clpBb0dBWmtIRWV5em16Z2cxR3dtTzN3bmljSHRMR1BQRFhiSW53NTdsdkIrY3lyd0FrVEs1MlFScTM0VkMKRDhnQWFwMTU2OWlWUER3YlgrNkpBQk1WQ2tNUmdxMjdHanUzN0pVY2Fib2g1YzJQeTBYNUlhUG8rek1hWHgvQwpqbjUvUW5YandjU1MrRU5hL1lXVWcxWEVjQjJYdEM0UExCdGUycitrUTVLbFNOREcxSTQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==`
kubeletconf.Write([]byte(kubeletconfDef))
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := fmt.Sprintf(`{
"name":"node-cni-network",
"type":"multus",
@@ -836,10 +807,6 @@ users:
Expect(netConf.Delegates[0].Conf.Type).To(Equal("mynet2"))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -854,7 +821,7 @@ users:
})
It("Errors when namespace isolation is violated", func() {
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -875,10 +842,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -902,7 +865,7 @@ users:
})
It("Properly allows a specified namespace reference when namespace isolation is enabled", func() {
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -924,10 +887,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -958,7 +917,7 @@ users:
Context("getDefaultNetDelegateCRD", func() {
It("fails when netConf contains bad confDir", func() {
fakePod := testutils.NewFakePod("testpod", "", "net1")
fakePod := testutils.NewFakePod(fakePodName, "", "net1")
conf := `{
"name":"node-cni-network",
"type":"multus",
@@ -969,10 +928,6 @@ users:
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -992,9 +947,9 @@ users:
Context("GetK8sArgs", func() {
It("fails when provided with bad format", func() {
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME:%s;K8S_POD_NAMESPACE:%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
args = &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME:%s;K8S_POD_NAMESPACE:%s;K8S_POD_UID:%s", fakePod.Name, fakePod.Namespace, fakePod.UID),
}
// using colon instead of equals sign makes an invalid CmdArgs
@@ -1005,7 +960,7 @@ users:
Context("getKubernetesDelegate", func() {
It("failed to get a ResourceClient instance", func() {
fakePod := testutils.NewFakePod("testpod", "net1,net2", "")
fakePod := testutils.NewFakePod(fakePodName, "net1,net2", "")
net1 := `{
"name": "net1",
"type": "mynet",
@@ -1021,9 +976,6 @@ users:
"type": "mynet3",
"cniVersion": "0.2.0"
}`
// args := &skel.CmdArgs{
// Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
// }
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
@@ -1050,44 +1002,35 @@ users:
})
Context("parsePodNetworkObjectName", func() {
It("fails to get podnetwork given bad annotation values", func() {
fakePod := testutils.NewFakePod("testpod", "net1", "")
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
_, err = clientInfo.AddNetAttachDef(
testutils.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net1", "{\"type\": \"mynet\"}"))
Expect(err).NotTo(HaveOccurred())
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
pod, err := clientInfo.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
// invalid case 1 - can't have more than 2 items separated by "/"
pod.Annotations[networkAttachmentAnnot] = "root@someIP/root@someOtherIP/root@thirdIP"
DescribeTable("fails to get podnetwork given bad annotation values", func(networkAnnot string) {
pod := testutils.NewFakePod(fakePodName, "net1", "")
pod.Annotations[networkAttachmentAnnot] = networkAnnot
_, err = GetPodNetwork(pod)
Expect(err).To(HaveOccurred())
},
Entry("can't have more than 2 items separated by \"/\"", "root@someIP/root@someOtherIP/root@thirdIP"),
Entry("can't have more than 2 items separated by \"@\"", "root@someIP/root@someOtherIP@garbagevalue"),
Entry("not matching comma-delimited format", "root@someIP/root@someOtherIP"),
Entry("invalid network interface name space in netdev name", "default/net1@myIfc Name"),
Entry("invalid network interface name too long", "default/net1@very_long_interface_name"),
)
// invalid case 2 - can't have more than 2 items separated by "@"
pod.Annotations[networkAttachmentAnnot] = "root@someIP/root@someOtherIP@garbagevalue"
DescribeTable("gets pod network successfully from annotation values", func(networkAnnot string) {
pod := testutils.NewFakePod(fakePodName, "net1", "")
pod.Annotations[networkAttachmentAnnot] = networkAnnot
_, err = GetPodNetwork(pod)
Expect(err).To(HaveOccurred())
// invalid case 3 - not matching comma-delimited format
pod.Annotations[networkAttachmentAnnot] = "root@someIP/root@someOtherIP"
_, err = GetPodNetwork(pod)
Expect(err).To(HaveOccurred())
})
Expect(err).ToNot(HaveOccurred())
},
Entry("network without namespace", "net1"),
Entry("network with namespace", "default/net1"),
Entry("network with interface name", "net1@my_interface"),
Entry("network with interface name and namespace", "default/net1@my_interface"),
)
})
Context("setPodNetworkAnnotation", func() {
It("Sets pod network annotations without error", func() {
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
net1 := `{
"name": "net1",
@@ -1095,10 +1038,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -1172,10 +1111,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err := clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -1183,6 +1118,9 @@ users:
_, err = clientInfo.AddNetAttachDef(testutils.NewFakeNetAttachDef("kube-system", "net1", net1))
Expect(err).NotTo(HaveOccurred())
args = &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s;K8S_POD_UID=%s", fakePod.Name, fakePod.Namespace, "blahblah"),
}
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
@@ -1209,7 +1147,7 @@ users:
// TODO Still figuring this next one out. deals with exponentialBackoff
// It("Fails to set pod network annotations without error", func() {
// fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
// fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
// net1 := `{
// "name": "net1",
@@ -1217,10 +1155,6 @@ users:
// "cniVersion": "0.2.0"
// }`
// args := &skel.CmdArgs{
// Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
// }
// clientInfo := NewFakeClientInfo()
// _, err := clientInfo.AddPod(fakePod)
// Expect(err).NotTo(HaveOccurred())
@@ -1240,7 +1174,7 @@ users:
})
Context("SetNetworkStatus", func() {
It("Sets network status without error", func() {
It("Sets network status without error when pod UIDs match", func() {
result := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
@@ -1265,13 +1199,16 @@ users:
delegate, err := types.LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.0", "")
Expect(err).NotTo(HaveOccurred())
delegateNetStatus, err := netutils.CreateNetworkStatus(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatus %+v\n", delegateNetStatus)
delegateNetStatuses, err := netutils.CreateNetworkStatuses(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatuses %+v\n", delegateNetStatuses)
Expect(err).NotTo(HaveOccurred())
netstatus := []nettypes.NetworkStatus{*delegateNetStatus}
netstatus := make([]nettypes.NetworkStatus, 0)
for _, status := range delegateNetStatuses {
netstatus = append(netstatus, *status)
}
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
@@ -1282,10 +1219,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -1299,6 +1232,129 @@ users:
Expect(err).NotTo(HaveOccurred())
})
It("Sets pod network annotations without error when runtime does not provide a pod UID", func() {
result := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testutils.EnsureCIDR("1.1.1.2/24"),
},
}
conf := `{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"type": "weave-net"
}],
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}`
delegate, err := types.LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.0", "")
Expect(err).NotTo(HaveOccurred())
delegateNetStatuses, err := netutils.CreateNetworkStatuses(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatuses %+v\n", delegateNetStatuses)
Expect(err).NotTo(HaveOccurred())
netstatus := make([]nettypes.NetworkStatus, 0)
for _, status := range delegateNetStatuses {
netstatus = append(netstatus, *status)
}
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
_, err = clientInfo.AddNetAttachDef(testutils.NewFakeNetAttachDef("kube-system", "net1", net1))
Expect(err).NotTo(HaveOccurred())
args = &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.Name, fakePod.Namespace),
}
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = SetNetworkStatus(clientInfo, k8sArgs, netstatus, netConf)
Expect(err).NotTo(HaveOccurred())
})
It("Fails to set pod network annotations when pod UIDs don't match", func() {
result := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testutils.EnsureCIDR("1.1.1.2/24"),
},
}
conf := `{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"type": "weave-net"
}],
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
}`
delegate, err := types.LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.0", "")
Expect(err).NotTo(HaveOccurred())
delegateNetStatuses, err := netutils.CreateNetworkStatuses(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatuses %+v\n", delegateNetStatuses)
Expect(err).NotTo(HaveOccurred())
netstatus := make([]nettypes.NetworkStatus, 0)
for _, status := range delegateNetStatuses {
netstatus = append(netstatus, *status)
}
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
_, err = clientInfo.AddNetAttachDef(testutils.NewFakeNetAttachDef("kube-system", "net1", net1))
Expect(err).NotTo(HaveOccurred())
args = &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s;K8S_POD_UID=%s", fakePod.Name, fakePod.Namespace, "foobar"),
}
k8sArgs, err := GetK8sArgs(args)
Expect(err).NotTo(HaveOccurred())
err = SetNetworkStatus(clientInfo, k8sArgs, netstatus, netConf)
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("expected pod %s/%s UID %q but got %q from Kube API",
fakePod.Namespace, fakePod.Name, string(k8sArgs.K8S_POD_UID), fakePod.UID)))
})
It("Sets network status with kubeclient built from kubeconfig and attempts to connect", func() {
kubeletconf, err := os.Create("/etc/kubernetes/kubelet.conf")
kubeletconfDef := `apiVersion: v1
@@ -1347,13 +1403,16 @@ users:
delegate, err := types.LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.0", "")
Expect(err).NotTo(HaveOccurred())
delegateNetStatus, err := netutils.CreateNetworkStatus(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatus %+v\n", delegateNetStatus)
delegateNetStatuses, err := netutils.CreateNetworkStatuses(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatuses %+v\n", delegateNetStatuses)
Expect(err).NotTo(HaveOccurred())
netstatus := []nettypes.NetworkStatus{*delegateNetStatus}
netstatus := make([]nettypes.NetworkStatus, 0)
for _, status := range delegateNetStatuses {
netstatus = append(netstatus, *status)
}
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
@@ -1364,10 +1423,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -1407,13 +1462,16 @@ users:
delegate, err := types.LoadDelegateNetConf([]byte(conf), nil, "", "")
Expect(err).NotTo(HaveOccurred())
delegateNetStatus, err := netutils.CreateNetworkStatus(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatus %+v\n", delegateNetStatus)
delegateNetStatuses, err := netutils.CreateNetworkStatuses(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatuses %+v\n", delegateNetStatuses)
Expect(err).NotTo(HaveOccurred())
netstatus := []nettypes.NetworkStatus{*delegateNetStatus}
netstatus := make([]nettypes.NetworkStatus, 0)
for _, status := range delegateNetStatuses {
netstatus = append(netstatus, *status)
}
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
@@ -1424,10 +1482,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
@@ -1466,13 +1520,16 @@ users:
delegate, err := types.LoadDelegateNetConf([]byte(conf), nil, "0000:00:00.0", "")
Expect(err).NotTo(HaveOccurred())
delegateNetStatus, err := netutils.CreateNetworkStatus(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatus %+v\n", delegateNetStatus)
delegateNetStatuses, err := netutils.CreateNetworkStatuses(result, delegate.Conf.Name, delegate.MasterPlugin, nil)
GinkgoT().Logf("delegateNetStatuses %+v\n", delegateNetStatuses)
Expect(err).NotTo(HaveOccurred())
netstatus := []nettypes.NetworkStatus{*delegateNetStatus}
netstatus := make([]nettypes.NetworkStatus, 0)
for _, status := range delegateNetStatuses {
netstatus = append(netstatus, *status)
}
fakePod := testutils.NewFakePod("testpod", "kube-system/net1", "")
fakePod := testutils.NewFakePod(fakePodName, "kube-system/net1", "")
netConf, err := types.LoadNetConf([]byte(conf))
Expect(err).NotTo(HaveOccurred())
@@ -1483,10 +1540,6 @@ users:
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
}
clientInfo := NewFakeClientInfo()
_, err = clientInfo.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())

247
pkg/k8sclient/kubeconfig.go Normal file
View File

@@ -0,0 +1,247 @@
// Copyright (c) 2023 Multus 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 k8sclient
import (
"context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"os"
"path"
"strings"
"time"
certificatesv1 "k8s.io/api/certificates/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/transport"
"k8s.io/client-go/util/certificate"
"k8s.io/klog"
netclient "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
)
const (
certNamePrefix = "multus-client"
certCommonNamePrefix = "system:multus"
certOrganization = "system:multus"
)
var (
certUsages = []certificatesv1.KeyUsage{certificatesv1.UsageDigitalSignature, certificatesv1.UsageClientAuth}
)
// getPerNodeKubeconfig creates new kubeConfig, based on bootstrap, with new certDir
func getPerNodeKubeconfig(bootstrap *rest.Config, certDir string) *rest.Config {
return &rest.Config{
Host: bootstrap.Host,
APIPath: bootstrap.APIPath,
ContentConfig: rest.ContentConfig{
AcceptContentTypes: "application/vnd.kubernetes.protobuf,application/json",
ContentType: "application/vnd.kubernetes.protobuf",
},
TLSClientConfig: rest.TLSClientConfig{
KeyFile: path.Join(certDir, certNamePrefix+"-current.pem"),
CertFile: path.Join(certDir, certNamePrefix+"-current.pem"),
CAData: bootstrap.TLSClientConfig.CAData,
},
// Allow multus (especially in server mode) to make more concurrent requests
// to reduce client-side throttling
QPS: 50,
Burst: 50,
// Set the config timeout to one minute.
Timeout: time.Minute,
}
}
// PerNodeK8sClient creates/reload new multus kubeconfig per-node.
func PerNodeK8sClient(nodeName, bootstrapKubeconfigFile string, certDuration time.Duration, certDir string) (*ClientInfo, error) {
bootstrapKubeconfig, err := clientcmd.BuildConfigFromFlags("", bootstrapKubeconfigFile)
if err != nil {
return nil, logging.Errorf("failed to load bootstrap kubeconfig %s: %v", bootstrapKubeconfigFile, err)
}
config := getPerNodeKubeconfig(bootstrapKubeconfig, certDir)
// If we have a valid certificate, user that to fetch CSRs.
// Otherwise, use the bootstrap credentials from bootstrapKubeconfig
// https://github.com/kubernetes/kubernetes/blob/068ee321bc7bfe1c2cefb87fb4d9e5deea84fbc8/cmd/kubelet/app/server.go#L953-L963
newClientsetFn := func(current *tls.Certificate) (kubernetes.Interface, error) {
cfg := bootstrapKubeconfig
// validate the kubeconfig
tempClient, err := kubernetes.NewForConfig(cfg)
if err != nil {
logging.Errorf("failed to read kubeconfig from cert manager: %v", err)
} else {
_, err := tempClient.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
// tls unknown authority error is unrecoverable error with retry
if err != nil {
if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
logging.Verbosef("cert mgr gets invalid config. rebuild from bootstrap kubeconfig")
// reload and use bootstrapKubeconfig again
newBootstrapKubeconfig, _ := clientcmd.BuildConfigFromFlags("", bootstrapKubeconfigFile)
cfg = newBootstrapKubeconfig
} else {
logging.Errorf("failed to list pods with new certs: %v", err)
}
}
if current != nil {
cfg = config
}
}
return kubernetes.NewForConfig(cfg)
}
certificateStore, err := certificate.NewFileStore(certNamePrefix, certDir, certDir, "", "")
if err != nil {
return nil, logging.Errorf("failed to initialize the certificate store: %v", err)
}
certManager, err := certificate.NewManager(&certificate.Config{
ClientsetFn: newClientsetFn,
Template: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("%s:%s", certCommonNamePrefix, nodeName),
Organization: []string{certOrganization},
},
},
RequestedCertificateLifetime: &certDuration,
SignerName: certificatesv1.KubeAPIServerClientSignerName,
Usages: certUsages,
CertificateStore: certificateStore,
})
if err != nil {
return nil, logging.Errorf("failed to initialize the certificate manager: %v", err)
}
if certDuration < time.Hour {
// the default value for CertCallbackRefreshDuration (5min) is too long for short-lived certs,
// set it to a more sensible value
transport.CertCallbackRefreshDuration = time.Second * 10
}
certManager.Start()
logging.Verbosef("Waiting for certificate")
var storeErr error
err = wait.PollWithContext(context.TODO(), time.Second, 2*time.Minute, func(_ context.Context) (bool, error) {
var currentCert *tls.Certificate
currentCert, storeErr = certificateStore.Current()
return currentCert != nil && storeErr == nil, nil
})
if err != nil {
return nil, logging.Errorf("certificate was not signed, last cert store err: %v err: %v", storeErr, err)
}
logging.Verbosef("Certificate found!")
return newClientInfo(config)
}
// InClusterK8sClient returns the `k8s.ClientInfo` struct to use to connect to
// the k8s API.
func InClusterK8sClient() (*ClientInfo, error) {
clientInfo, err := GetK8sClient("", nil)
if err != nil {
return nil, err
}
if clientInfo == nil {
return nil, fmt.Errorf("failed to create in-cluster kube client")
}
return clientInfo, err
}
// SetK8sClientInformers adds informer structure to ClientInfo to utilize in thick daemon
func (c *ClientInfo) SetK8sClientInformers(podInformer, netDefInformer cache.SharedIndexInformer) {
c.PodInformer = podInformer
c.NetDefInformer = netDefInformer
}
// GetK8sClient gets client info from kubeconfig
func GetK8sClient(kubeconfig string, kubeClient *ClientInfo) (*ClientInfo, error) {
logging.Debugf("GetK8sClient: %s, %v", kubeconfig, kubeClient)
// If we get a valid kubeClient (eg from testcases) just return that
// one.
if kubeClient != nil {
return kubeClient, nil
}
var err error
var config *rest.Config
// Otherwise try to create a kubeClient from a given kubeConfig
if kubeconfig != "" {
// uses the current context in kubeconfig
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, logging.Errorf("GetK8sClient: failed to get context for the kubeconfig %v: %v", kubeconfig, err)
}
} else if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
// Try in-cluster config where multus might be running in a kubernetes pod
config, err = rest.InClusterConfig()
if err != nil {
return nil, logging.Errorf("GetK8sClient: failed to get context for in-cluster kube config: %v", err)
}
} else {
// No kubernetes config; assume we shouldn't talk to Kube at all
return nil, nil
}
// Specify that we use gRPC
config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
config.ContentType = "application/vnd.kubernetes.protobuf"
// Set the config timeout to one minute.
config.Timeout = time.Minute
// Allow multus (especially in server mode) to make more concurrent requests
// to reduce client-side throttling
config.QPS = 50
config.Burst = 50
return newClientInfo(config)
}
// newClientInfo returns a `ClientInfo` from a configuration created from an
// existing kubeconfig file.
func newClientInfo(config *rest.Config) (*ClientInfo, error) {
client, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
netclient, err := netclient.NewForConfig(config)
if err != nil {
return nil, err
}
broadcaster := record.NewBroadcaster()
broadcaster.StartLogging(klog.Infof)
broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "multus"})
return &ClientInfo{
Client: client,
NetClient: netclient,
EventBroadcaster: broadcaster,
EventRecorder: recorder,
}, nil
}

17
pkg/kubeletclient/doc.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Multus 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 kubeletclient is the package that contains the kubelet's libraries that
// controls podresource API in kubelet
package kubeletclient

View File

@@ -1,49 +1,99 @@
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Multus 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 kubeletclient
import (
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/net/context"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/checkpoint"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/checkpoint"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
v1 "k8s.io/api/core/v1"
podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1"
"k8s.io/kubernetes/pkg/kubelet/apis/podresources"
"k8s.io/kubernetes/pkg/kubelet/util"
)
const (
defaultKubeletSocketFile = "kubelet.sock"
defaultKubeletSocket = "kubelet" // which is defined in k8s.io/kubernetes/pkg/kubelet/apis/podresources
kubeletConnectionTimeout = 10 * time.Second
defaultPodResourcesMaxSize = 1024 * 1024 * 16 // 16 Mb
defaultPodResourcesPath = "/var/lib/kubelet/pod-resources"
unixProtocol = "unix"
)
// LocalEndpoint returns the full path to a unix socket at the given endpoint
// which is in k8s.io/kubernetes/pkg/kubelet/util
func localEndpoint(path string) *url.URL {
return &url.URL{
Scheme: unixProtocol,
Path: path + ".sock",
}
}
// GetResourceClient returns an instance of ResourceClient interface initialized with Pod resource information
func GetResourceClient(kubeletSocket string) (types.ResourceClient, error) {
if kubeletSocket == "" {
kubeletSocket, _ = util.LocalEndpoint(defaultPodResourcesPath, podresources.Socket)
kubeletSocketURL := localEndpoint(filepath.Join(defaultPodResourcesPath, defaultKubeletSocket))
if kubeletSocket != "" {
kubeletSocketURL = &url.URL{
Scheme: unixProtocol,
Path: kubeletSocket,
}
}
// If Kubelet resource API endpoint exist use that by default
// Or else fallback with checkpoint file
if hasKubeletAPIEndpoint(kubeletSocket) {
if hasKubeletAPIEndpoint(kubeletSocketURL) {
logging.Debugf("GetResourceClient: using Kubelet resource API endpoint")
return getKubeletClient(kubeletSocket)
return getKubeletClient(kubeletSocketURL)
}
logging.Debugf("GetResourceClient: using Kubelet device plugin checkpoint")
return checkpoint.GetCheckpoint()
}
func getKubeletClient(kubeletSocket string) (types.ResourceClient, error) {
newClient := &kubeletClient{}
if kubeletSocket == "" {
kubeletSocket, _ = util.LocalEndpoint(defaultPodResourcesPath, podresources.Socket)
}
func dial(ctx context.Context, addr string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, unixProtocol, addr)
}
client, conn, err := podresources.GetV1Client(kubeletSocket, 10*time.Second, defaultPodResourcesMaxSize)
func getKubeletResourceClient(kubeletSocketURL *url.URL, timeout time.Duration) (podresourcesapi.PodResourcesListerClient, *grpc.ClientConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
conn, err := grpc.DialContext(ctx, kubeletSocketURL.Path, grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dial),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaultPodResourcesMaxSize)))
if err != nil {
return nil, nil, fmt.Errorf("error dialing socket %s: %v", kubeletSocketURL.Path, err)
}
return podresourcesapi.NewPodResourcesListerClient(conn), conn, nil
}
func getKubeletClient(kubeletSocketURL *url.URL) (types.ResourceClient, error) {
newClient := &kubeletClient{}
client, conn, err := getKubeletResourceClient(kubeletSocketURL, 10*time.Second)
if err != nil {
return nil, logging.Errorf("getKubeletClient: error getting grpc client: %v\n", err)
}
@@ -82,32 +132,54 @@ func (rc *kubeletClient) GetPodResourceMap(pod *v1.Pod) (map[string]*types.Resou
ns := pod.Namespace
if name == "" || ns == "" {
return nil, logging.Errorf("GetPodResourcesMap: Pod name or namespace cannot be empty")
return nil, logging.Errorf("GetPodResourceMap: Pod name or namespace cannot be empty")
}
for _, pr := range rc.resources {
if pr.Name == name && pr.Namespace == ns {
for _, cnt := range pr.Containers {
for _, dev := range cnt.Devices {
if rInfo, ok := resourceMap[dev.ResourceName]; ok {
rInfo.DeviceIDs = append(rInfo.DeviceIDs, dev.DeviceIds...)
} else {
resourceMap[dev.ResourceName] = &types.ResourceInfo{DeviceIDs: dev.DeviceIds}
}
}
rc.getDevicePluginResources(cnt.Devices, resourceMap)
rc.getDRAResources(cnt.DynamicResources, resourceMap)
}
}
}
return resourceMap, nil
}
func hasKubeletAPIEndpoint(endpoint string) bool {
u, err := url.Parse(endpoint)
if err != nil {
return false
func (rc *kubeletClient) getDevicePluginResources(devices []*podresourcesapi.ContainerDevices, resourceMap map[string]*types.ResourceInfo) {
for _, dev := range devices {
if rInfo, ok := resourceMap[dev.ResourceName]; ok {
rInfo.DeviceIDs = append(rInfo.DeviceIDs, dev.DeviceIds...)
} else {
resourceMap[dev.ResourceName] = &types.ResourceInfo{DeviceIDs: dev.DeviceIds}
}
}
}
func (rc *kubeletClient) getDRAResources(dynamicResources []*podresourcesapi.DynamicResource, resourceMap map[string]*types.ResourceInfo) {
for _, dynamicResource := range dynamicResources {
var deviceIDs []string
for _, claimResource := range dynamicResource.ClaimResources {
for _, cdiDevice := range claimResource.CDIDevices {
res := strings.Split(cdiDevice.Name, "=")
if len(res) == 2 {
deviceIDs = append(deviceIDs, res[1])
} else {
logging.Errorf("GetPodResourceMap: Invalid CDI format")
}
}
}
if rInfo, ok := resourceMap[dynamicResource.ClassName]; ok {
rInfo.DeviceIDs = append(rInfo.DeviceIDs, deviceIDs...)
} else {
resourceMap[dynamicResource.ClassName] = &types.ResourceInfo{DeviceIDs: deviceIDs}
}
}
}
func hasKubeletAPIEndpoint(url *url.URL) bool {
// Check for kubelet resource API socket file
if _, err := os.Stat(u.Path); err != nil {
if _, err := os.Stat(url.Path); err != nil {
logging.Debugf("hasKubeletAPIEndpoint: error looking up kubelet resource api socket file: %q", err)
return false
}

View File

@@ -1,21 +1,41 @@
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Multus 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 kubeletclient
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"context"
"io/ioutil"
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"golang.org/x/sys/unix"
"google.golang.org/grpc"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubelet/util"
mtypes "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
mtypes "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1"
)
@@ -29,18 +49,17 @@ type fakeResourceServer struct {
server *grpc.Server
}
/* This is for 1.21.x or later. Uncomment it once we update vendor here!
//TODO: This is stub code for test, but we may need to change for the testing we use this API in the future...
func (m *fakeResourceServer) GetAllocatableResources(ctx context.Context, req *podresourcesapi.AllocatableResourcesRequest) (*podresourcesapi.AllocatableResourcesResponse, error) {
// TODO: This is stub code for test, but we may need to change for the testing we use this API in the future...
func (m *fakeResourceServer) GetAllocatableResources(_ context.Context, _ *podresourcesapi.AllocatableResourcesRequest) (*podresourcesapi.AllocatableResourcesResponse, error) {
return &podresourcesapi.AllocatableResourcesResponse{}, nil
}
*/
func (m *fakeResourceServer) List(ctx context.Context, req *podresourcesapi.ListPodResourcesRequest) (*podresourcesapi.ListPodResourcesResponse, error) {
podName := "pod-name"
podNamespace := "pod-namespace"
containerName := "container-name"
// TODO: This is stub code for test, but we may need to change for the testing we use this API in the future...
func (m *fakeResourceServer) Get(_ context.Context, _ *podresourcesapi.GetPodResourcesRequest) (*podresourcesapi.GetPodResourcesResponse, error) {
return &podresourcesapi.GetPodResourcesResponse{}, nil
}
func (m *fakeResourceServer) List(_ context.Context, _ *podresourcesapi.ListPodResourcesRequest) (*podresourcesapi.ListPodResourcesResponse, error) {
devs := []*podresourcesapi.ContainerDevices{
{
ResourceName: "resource",
@@ -48,18 +67,49 @@ func (m *fakeResourceServer) List(ctx context.Context, req *podresourcesapi.List
},
}
cdiDevices := []*podresourcesapi.CDIDevice{
{
Name: "cdi-kind=cdi-resource",
},
}
claimsResource := []*podresourcesapi.ClaimResource{
{
CDIDevices: cdiDevices,
},
}
dynamicResources := []*podresourcesapi.DynamicResource{
{
ClassName: "resource-class",
ClaimName: "resource-claim",
ClaimNamespace: "dynamic-resource-pod-namespace",
ClaimResources: claimsResource,
},
}
resp := &podresourcesapi.ListPodResourcesResponse{
PodResources: []*podresourcesapi.PodResources{
{
Name: podName,
Namespace: podNamespace,
Name: "pod-name",
Namespace: "pod-namespace",
Containers: []*podresourcesapi.ContainerResources{
{
Name: containerName,
Name: "container-name",
Devices: devs,
},
},
},
{
Name: "dynamic-resource-pod-name",
Namespace: "dynamic-resource-pod-namespace",
Containers: []*podresourcesapi.ContainerResources{
{
Name: "dynamic-resource-container-name",
DynamicResources: dynamicResources,
},
},
},
},
}
return resp, nil
@@ -70,9 +120,45 @@ func TestKubeletclient(t *testing.T) {
RunSpecs(t, "Kubeletclient Suite")
}
var testKubeletSocket string
var testKubeletSocket *url.URL
// CreateListener creates a listener on the specified endpoint.
// based from k8s.io/kubernetes/pkg/kubelet/util
func CreateListener(addr string) (net.Listener, error) {
// Unlink to cleanup the previous socket file.
err := unix.Unlink(addr)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to unlink socket file %q: %v", addr, err)
}
if err := os.MkdirAll(filepath.Dir(addr), 0750); err != nil {
return nil, fmt.Errorf("error creating socket directory %q: %v", filepath.Dir(addr), err)
}
// Create the socket on a tempfile and move it to the destination socket to handle improper cleanup
file, err := os.CreateTemp(filepath.Dir(addr), "")
if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %v", err)
}
if err := os.Remove(file.Name()); err != nil {
return nil, fmt.Errorf("failed to remove temporary file: %v", err)
}
l, err := net.Listen(unixProtocol, file.Name())
if err != nil {
return nil, err
}
if err = os.Rename(file.Name(), addr); err != nil {
return nil, fmt.Errorf("failed to move temporary file to addr %q: %v", addr, err)
}
return l, nil
}
func setUp() error {
tempSocketDir, err := ioutil.TempDir("", "kubelet-resource-client")
tempSocketDir, err := os.MkdirTemp("", "kubelet-resource-client")
if err != nil {
return err
}
@@ -84,11 +170,11 @@ func setUp() error {
socketDir = testingPodResourcesPath
socketName = filepath.Join(socketDir, "kubelet.sock")
testKubeletSocket = socketName
testKubeletSocket = localEndpoint(filepath.Join(socketDir, "kubelet"))
fakeServer = &fakeResourceServer{server: grpc.NewServer()}
podresourcesapi.RegisterPodResourcesListerServer(fakeServer.server, fakeServer)
lis, err := util.CreateListener(socketName)
lis, err := CreateListener(socketName)
if err != nil {
return err
}
@@ -118,18 +204,18 @@ var _ = Describe("Kubelet resource endpoint data read operations", func() {
Context("GetResourceClient()", func() {
It("should return no error", func() {
_, err := GetResourceClient(testKubeletSocket)
_, err := GetResourceClient(testKubeletSocket.Path)
Expect(err).NotTo(HaveOccurred())
})
It("should fail with missing file", func() {
_, err := GetResourceClient("sampleSocketString")
_, err := GetResourceClient("unix:/sampleSocketString")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("error reading file"))
})
})
Context("GetPodResourceMap() with valid pod name and namespace", func() {
It("should return no error", func() {
It("should return no error with device plugin resource", func() {
podUID := k8sTypes.UID("970a395d-bb3b-11e8-89df-408d5c537d23")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
@@ -157,8 +243,38 @@ var _ = Describe("Kubelet resource endpoint data read operations", func() {
Expect(resourceMap).To(Equal(outputRMap))
})
It("should return no error with dynamic resource", func() {
podUID := k8sTypes.UID("9f94e27b-4233-43d6-bd10-f73b4de6f456")
fakePod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "dynamic-resource-pod-name",
Namespace: "dynamic-resource-pod-namespace",
UID: podUID,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "dynamic-resource-container-name",
},
},
},
}
client, err := getKubeletClient(testKubeletSocket)
Expect(err).NotTo(HaveOccurred())
outputRMap := map[string]*mtypes.ResourceInfo{
"resource-class": {DeviceIDs: []string{"cdi-resource"}},
}
resourceMap, err := client.GetPodResourceMap(fakePod)
Expect(err).NotTo(HaveOccurred())
Expect(resourceMap).ShouldNot(BeNil())
Expect(resourceMap).To(Equal(outputRMap))
})
It("should return an error with garbage socket value", func() {
_, err := getKubeletClient("/badfilepath!?//")
u, err := url.Parse("/badfilepath!?//")
Expect(err).NotTo(HaveOccurred())
_, err = getKubeletClient(u)
Expect(err).To(HaveOccurred())
})
})

16
pkg/logging/doc.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2022 Multus 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 logging is the package that contains logging library.
package logging

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,13 +16,13 @@
package logging
import (
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/pkg/errors"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
)
@@ -41,9 +42,52 @@ const (
var loggingStderr bool
var loggingW io.Writer
var loggingLevel Level
var logger *lumberjack.Logger
const defaultTimestampFormat = time.RFC3339
// LogOptions specifies the configuration of the log
type LogOptions struct {
MaxAge *int `json:"maxAge,omitempty"`
MaxSize *int `json:"maxSize,omitempty"`
MaxBackups *int `json:"maxBackups,omitempty"`
Compress *bool `json:"compress,omitempty"`
}
// SetLogOptions set the LoggingOptions of NetConf
func SetLogOptions(options *LogOptions) {
// logger is used only if filname is supplied
if logger == nil || logger.Filename == "" {
return
}
// give some default value
updatedLogger := lumberjack.Logger{
Filename: logger.Filename,
MaxAge: 5,
MaxBackups: 5,
Compress: true,
MaxSize: 100,
LocalTime: logger.LocalTime,
}
if options != nil {
if options.MaxAge != nil {
updatedLogger.MaxAge = *options.MaxAge
}
if options.MaxSize != nil {
updatedLogger.MaxSize = *options.MaxSize
}
if options.MaxBackups != nil {
updatedLogger.MaxBackups = *options.MaxBackups
}
if options.Compress != nil {
updatedLogger.Compress = *options.Compress
}
}
logger = &updatedLogger
loggingW = logger
}
func (l Level) String() string {
switch l {
case PanicLevel:
@@ -137,22 +181,32 @@ func SetLogStderr(enable bool) {
// SetLogFile sets logging file
func SetLogFile(filename string) {
// logger is used only if filname is supplied
if filename == "" {
return
}
loggingW = &lumberjack.Logger{
updatedLogger := lumberjack.Logger{
Filename: filename,
MaxSize: 100, // megabytes
MaxAge: 5,
MaxBackups: 5,
MaxAge: 5, // days
Compress: true,
MaxSize: 100,
}
if logger != nil {
updatedLogger.MaxAge = logger.MaxAge
updatedLogger.MaxBackups = logger.MaxBackups
updatedLogger.Compress = logger.Compress
updatedLogger.MaxSize = logger.MaxSize
}
logger = &updatedLogger
loggingW = logger
}
func init() {
loggingStderr = true
loggingW = nil
loggingLevel = PanicLevel
logger = nil
}

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,10 +15,17 @@
package logging
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"fmt"
"os"
"testing"
. "github.com/onsi/ginkgo"
testutils "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/testing"
"gopkg.in/natefinch/lumberjack.v2"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -54,12 +62,16 @@ var _ = Describe("logging operations", func() {
It("Check loglevel setter", func() {
SetLogLevel("debug")
Expect(loggingLevel).To(Equal(DebugLevel))
Expect(loggingLevel.String()).To(Equal("debug"))
SetLogLevel("Error")
Expect(loggingLevel).To(Equal(ErrorLevel))
Expect(loggingLevel.String()).To(Equal("error"))
SetLogLevel("VERbose")
Expect(loggingLevel).To(Equal(VerboseLevel))
Expect(loggingLevel.String()).To(Equal("verbose"))
SetLogLevel("PANIC")
Expect(loggingLevel).To(Equal(PanicLevel))
Expect(loggingLevel.String()).To(Equal("panic"))
})
It("Check loglevel setter with invalid level", func() {
@@ -74,9 +86,90 @@ var _ = Describe("logging operations", func() {
Expect(loggingStderr).NotTo(Equal(currentVal))
})
It("Check log function is worked", func() {
Debugf("foobar")
Verbosef("foobar")
Expect(Errorf("foobar")).NotTo(BeNil())
Panicf("foobar")
})
It("Check log function is worked with stderr", func() {
SetLogStderr(true)
Debugf("foobar")
Verbosef("foobar")
Expect(Errorf("foobar")).NotTo(BeNil())
Panicf("foobar")
})
It("Check log function is worked with stderr", func() {
tmpDir, err := os.MkdirTemp("", "multus_tmp")
SetLogFile(fmt.Sprintf("%s/log.txt", tmpDir))
Debugf("foobar")
Verbosef("foobar")
Expect(Errorf("foobar")).NotTo(BeNil())
Panicf("foobar")
logger = nil
loggingW = nil
err = os.RemoveAll(tmpDir)
Expect(err).NotTo(HaveOccurred())
// Revert the log variable to init
loggingW = nil
logger = nil
})
// Tests public getter
It("Check getter for logging level with current level", func() {
currentLevel := loggingLevel
Expect(currentLevel).To(Equal(GetLoggingLevel()))
})
It("Check user settings logOptions for logging", func() {
SetLogFile("/var/log/multus.log")
expectLogger := &lumberjack.Logger{
Filename: "/var/log/multus.log",
MaxAge: 1,
MaxSize: 10,
MaxBackups: 1,
Compress: true,
}
logOptions := &LogOptions{
MaxAge: testutils.Int(1),
MaxSize: testutils.Int(10),
MaxBackups: testutils.Int(1),
Compress: testutils.Bool(true),
}
SetLogOptions(logOptions)
Expect(expectLogger).To(Equal(logger))
})
It("Check user settings logOptions and missing some options", func() {
SetLogFile("/var/log/multus.log")
expectLogger := &lumberjack.Logger{
Filename: "/var/log/multus.log",
MaxAge: 5,
MaxSize: 100,
MaxBackups: 1,
Compress: true,
}
logOptions := &LogOptions{
MaxBackups: testutils.Int(1),
Compress: testutils.Bool(true),
}
SetLogOptions(logOptions)
Expect(expectLogger).To(Equal(logger))
})
It("Check user don't settings logOptions for logging", func() {
SetLogFile("/var/log/multus.log")
logger1 := &lumberjack.Logger{
Filename: "/var/log/multus.log",
MaxAge: 5,
MaxSize: 100,
MaxBackups: 5,
Compress: true,
}
SetLogOptions(nil)
Expect(logger1).To(Equal(logger))
})
})

17
pkg/multus/doc.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Multus 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 multus is the package that contains main multus function, which
// manipulates CNI request for delegate plugins.
package multus

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2017 Intel Corporation
// Copyright (c) 2016 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,7 +19,6 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
@@ -29,41 +29,39 @@ import (
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
cnicurrent "github.com/containernetworking/cni/pkg/types/current"
cni100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns"
nettypes "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
nadutils "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils"
"github.com/vishvananda/netlink"
k8s "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/k8sclient"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/netutils"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
k8snet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait"
k8s "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/k8sclient"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/netutils"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
)
const (
shortPollDuration = 250 * time.Millisecond
shortPollTimeout = 2500 * time.Millisecond
shortPollDuration = 250 * time.Millisecond
informerPollDuration = 50 * time.Millisecond
shortPollTimeout = 2500 * time.Millisecond
)
var (
version = "master@git"
commit = "unknown commit"
date = "unknown date"
version = "master@git"
commit = "unknown commit"
date = "unknown date"
gitTreeState = ""
releaseStatus = ""
)
var (
pollDuration = 1000 * time.Millisecond
pollTimeout = 45 * time.Second
)
//PrintVersionString ...
// PrintVersionString ...
func PrintVersionString() string {
return fmt.Sprintf("multus-cni version:%s, commit:%s, date:%s",
version, commit, date)
return fmt.Sprintf("version:%s(%s%s), commit:%s, date:%s", version, gitTreeState, releaseStatus, commit, date)
}
func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
@@ -74,7 +72,7 @@ func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
path := filepath.Join(dataDir, containerID)
err := ioutil.WriteFile(path, netconf, 0600)
err := os.WriteFile(path, netconf, 0600)
if err != nil {
return logging.Errorf("saveScratchNetConf: failed to write container data in the path(%q): %v", path, err)
}
@@ -86,7 +84,7 @@ func consumeScratchNetConf(containerID, dataDir string) ([]byte, string, error)
logging.Debugf("consumeScratchNetConf: %s, %s", containerID, dataDir)
path := filepath.Join(dataDir, containerID)
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
return b, path, err
}
@@ -105,7 +103,7 @@ func getIfname(delegate *types.DelegateNetConf, argif string, idx int) string {
return fmt.Sprintf("net%d", idx)
}
func getDelegateDeviceInfo(delegate *types.DelegateNetConf, runtimeConf *libcni.RuntimeConf) (*nettypes.DeviceInfo, error) {
func getDelegateDeviceInfo(_ *types.DelegateNetConf, runtimeConf *libcni.RuntimeConf) (*nettypes.DeviceInfo, error) {
// If the DPDeviceInfoFile was created, it was copied to the CNIDeviceInfoFile.
// If the DPDeviceInfoFile was not created, CNI might have created it. So
// either way, load CNIDeviceInfoFile.
@@ -199,14 +197,14 @@ func confCheck(rt *libcni.RuntimeConf, rawNetconf []byte, multusNetconf *types.N
err = cniNet.CheckNetwork(context.Background(), conf, rt)
if err != nil {
return logging.Errorf("error in getting result from DelNetwork: %v", err)
return logging.Errorf("error in getting result from CheckNetwork: %v", err)
}
return err
}
func confDel(rt *libcni.RuntimeConf, rawNetconf []byte, multusNetconf *types.NetConf, exec invoke.Exec) error {
logging.Debugf("conflistDel: %v, %s", rt, string(rawNetconf))
logging.Debugf("confDel: %v, %s", rt, string(rawNetconf))
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go
binDirs := filepath.SplitList(os.Getenv("CNI_PATH"))
binDirs = append([]string{multusNetconf.BinDir}, binDirs...)
@@ -285,32 +283,24 @@ func conflistDel(rt *libcni.RuntimeConf, rawnetconflist []byte, multusNetconf *t
return err
}
func delegateAdd(exec invoke.Exec, kubeClient *k8s.ClientInfo, pod *v1.Pod, ifName string, delegate *types.DelegateNetConf, rt *libcni.RuntimeConf, multusNetconf *types.NetConf, cniArgs string) (cnitypes.Result, error) {
logging.Debugf("delegateAdd: %v, %s, %v, %v", exec, ifName, delegate, rt)
if os.Setenv("CNI_IFNAME", ifName) != nil {
return nil, logging.Errorf("delegateAdd: error setting environment variable CNI_IFNAME")
}
// DelegateAdd ...
func DelegateAdd(exec invoke.Exec, kubeClient *k8s.ClientInfo, pod *v1.Pod, delegate *types.DelegateNetConf, rt *libcni.RuntimeConf, multusNetconf *types.NetConf) (cnitypes.Result, error) {
logging.Debugf("DelegateAdd: %v, %v, %v", exec, delegate, rt)
if err := validateIfName(os.Getenv("CNI_NETNS"), ifName); err != nil {
return nil, logging.Errorf("delegateAdd: cannot set %q interface name to %q: %v", delegate.Conf.Type, ifName, err)
if err := validateIfName(rt.NetNS, rt.IfName); err != nil {
return nil, logging.Errorf("DelegateAdd: cannot set %q interface name to %q: %v", delegate.Conf.Type, rt.IfName, err)
}
// Deprecated in ver 3.5.
if delegate.MacRequest != "" || delegate.IPRequest != nil {
if cniArgs != "" {
cniArgs = fmt.Sprintf("%s;IgnoreUnknown=true", cniArgs)
} else {
cniArgs = "IgnoreUnknown=true"
}
if delegate.MacRequest != "" {
// validate Mac address
_, err := net.ParseMAC(delegate.MacRequest)
if err != nil {
return nil, logging.Errorf("delegateAdd: failed to parse mac address %q", delegate.MacRequest)
return nil, logging.Errorf("DelegateAdd: failed to parse mac address %q", delegate.MacRequest)
}
cniArgs = fmt.Sprintf("%s;MAC=%s", cniArgs, delegate.MacRequest)
logging.Debugf("delegateAdd: set MAC address %q to %q", delegate.MacRequest, ifName)
logging.Debugf("DelegateAdd: set MAC address %q to %q", delegate.MacRequest, rt.IfName)
rt.Args = append(rt.Args, [2]string{"MAC", delegate.MacRequest})
}
@@ -320,16 +310,15 @@ func delegateAdd(exec invoke.Exec, kubeClient *k8s.ClientInfo, pod *v1.Pod, ifNa
if strings.Contains(ip, "/") {
_, _, err := net.ParseCIDR(ip)
if err != nil {
return nil, logging.Errorf("delegateAdd: failed to parse IP address %q", ip)
return nil, logging.Errorf("DelegateAdd: failed to parse IP address %q", ip)
}
} else if net.ParseIP(ip) == nil {
return nil, logging.Errorf("delegateAdd: failed to parse IP address %q", ip)
return nil, logging.Errorf("DelegateAdd: failed to parse IP address %q", ip)
}
}
ips := strings.Join(delegate.IPRequest, ",")
cniArgs = fmt.Sprintf("%s;IP=%s", cniArgs, ips)
logging.Debugf("delegateAdd: set IP address %q to %q", ips, ifName)
logging.Debugf("DelegateAdd: set IP address %q to %q", ips, rt.IfName)
rt.Args = append(rt.Args, [2]string{"IP", ips})
}
}
@@ -366,9 +355,9 @@ func delegateAdd(exec invoke.Exec, kubeClient *k8s.ClientInfo, pod *v1.Pod, ifNa
// get IP addresses from result
ips := []string{}
res, err := cnicurrent.NewResultFromResult(result)
res, err := cni100.NewResultFromResult(result)
if err != nil {
logging.Errorf("delegateAdd: error converting result: %v", err)
logging.Errorf("DelegateAdd: error converting result: %v", err)
return result, nil
}
for _, ip := range res.IPs {
@@ -376,25 +365,25 @@ func delegateAdd(exec invoke.Exec, kubeClient *k8s.ClientInfo, pod *v1.Pod, ifNa
}
if pod != nil {
// send kubernetes events
if delegate.Name != "" {
kubeClient.Eventf(pod, v1.EventTypeNormal, "AddedInterface", "Add %s %v from %s", rt.IfName, ips, delegate.Name)
} else {
kubeClient.Eventf(pod, v1.EventTypeNormal, "AddedInterface", "Add %s %v", rt.IfName, ips)
// check Interfaces and IPs because some CNI plugin just return empty result
if res.Interfaces != nil || res.IPs != nil {
// send kubernetes events
if delegate.Name != "" {
kubeClient.Eventf(pod, v1.EventTypeNormal, "AddedInterface", "Add %s %v from %s", rt.IfName, ips, delegate.Name)
} else {
kubeClient.Eventf(pod, v1.EventTypeNormal, "AddedInterface", "Add %s %v", rt.IfName, ips)
}
}
} else {
// for further debug https://github.com/k8snetworkplumbingwg/multus-cni/issues/481
logging.Errorf("delegateAdd: pod nil pointer: namespace: %s, name: %s, container id: %s, pod: %v", rt.Args[1][1], rt.Args[2][1], rt.Args[3][1], pod)
logging.Errorf("DelegateAdd: pod nil pointer: namespace: %s, name: %s, container id: %s, pod: %v", rt.Args[1][1], rt.Args[2][1], rt.Args[3][1], pod)
}
return result, nil
}
func delegateCheck(exec invoke.Exec, ifName string, delegateConf *types.DelegateNetConf, rt *libcni.RuntimeConf, multusNetconf *types.NetConf) error {
logging.Debugf("delegateCheck: %v, %s, %v, %v", exec, ifName, delegateConf, rt)
if os.Setenv("CNI_IFNAME", ifName) != nil {
return logging.Errorf("delegateCheck: error setting environment variable CNI_IFNAME")
}
// DelegateCheck ...
func DelegateCheck(exec invoke.Exec, delegateConf *types.DelegateNetConf, rt *libcni.RuntimeConf, multusNetconf *types.NetConf) error {
logging.Debugf("DelegateCheck: %v, %v, %v", exec, delegateConf, rt)
if logging.GetLoggingLevel() >= logging.VerboseLevel {
var cniConfName string
@@ -410,23 +399,21 @@ func delegateCheck(exec invoke.Exec, ifName string, delegateConf *types.Delegate
if delegateConf.ConfListPlugin {
err = conflistCheck(rt, delegateConf.Bytes, multusNetconf, exec)
if err != nil {
return logging.Errorf("delegateCheck: error invoking ConflistCheck - %q: %v", delegateConf.ConfList.Name, err)
return logging.Errorf("DelegateCheck: error invoking ConflistCheck - %q: %v", delegateConf.ConfList.Name, err)
}
} else {
err = confCheck(rt, delegateConf.Bytes, multusNetconf, exec)
if err != nil {
return logging.Errorf("delegateCheck: error invoking DelegateCheck - %q: %v", delegateConf.Conf.Type, err)
return logging.Errorf("DelegateCheck: error invoking DelegateCheck - %q: %v", delegateConf.Conf.Type, err)
}
}
return err
}
func delegateDel(exec invoke.Exec, pod *v1.Pod, ifName string, delegateConf *types.DelegateNetConf, rt *libcni.RuntimeConf, multusNetconf *types.NetConf) error {
logging.Debugf("delegateDel: %v, %v, %s, %v, %v", exec, pod, ifName, delegateConf, rt)
if os.Setenv("CNI_IFNAME", ifName) != nil {
return logging.Errorf("delegateDel: error setting environment variable CNI_IFNAME")
}
// DelegateDel ...
func DelegateDel(exec invoke.Exec, pod *v1.Pod, delegateConf *types.DelegateNetConf, rt *libcni.RuntimeConf, multusNetconf *types.NetConf) error {
logging.Debugf("DelegateDel: %v, %v, %v, %v", exec, pod, delegateConf, rt)
if logging.GetLoggingLevel() >= logging.VerboseLevel {
var confName string
@@ -446,12 +433,12 @@ func delegateDel(exec invoke.Exec, pod *v1.Pod, ifName string, delegateConf *typ
if delegateConf.ConfListPlugin {
err = conflistDel(rt, delegateConf.Bytes, multusNetconf, exec)
if err != nil {
return logging.Errorf("delegateDel: error invoking ConflistDel - %q: %v", delegateConf.ConfList.Name, err)
return logging.Errorf("DelegateDel: error invoking ConflistDel - %q: %v", delegateConf.ConfList.Name, err)
}
} else {
err = confDel(rt, delegateConf.Bytes, multusNetconf, exec)
if err != nil {
return logging.Errorf("delegateDel: error invoking DelegateDel - %q: %v", delegateConf.Conf.Type, err)
return logging.Errorf("DelegateDel: error invoking DelegateDel - %q: %v", delegateConf.Conf.Type, err)
}
}
@@ -463,16 +450,13 @@ func delegateDel(exec invoke.Exec, pod *v1.Pod, ifName string, delegateConf *typ
// with each of the delegates' configuration
func delPlugins(exec invoke.Exec, pod *v1.Pod, args *skel.CmdArgs, k8sArgs *types.K8sArgs, delegates []*types.DelegateNetConf, lastIdx int, netRt *types.RuntimeConfig, multusNetconf *types.NetConf) error {
logging.Debugf("delPlugins: %v, %v, %v, %v, %v, %d, %v", exec, pod, args, k8sArgs, delegates, lastIdx, netRt)
if os.Setenv("CNI_COMMAND", "DEL") != nil {
return logging.Errorf("delPlugins: error setting environment variable CNI_COMMAND to a value of DEL")
}
var errorstrings []string
for idx := lastIdx; idx >= 0; idx-- {
ifName := getIfname(delegates[idx], args.IfName, idx)
rt, cniDeviceInfoPath := types.CreateCNIRuntimeConf(args, k8sArgs, ifName, netRt, delegates[idx])
// Attempt to delete all but do not error out, instead, collect all errors.
if err := delegateDel(exec, pod, ifName, delegates[idx], rt, multusNetconf); err != nil {
if err := DelegateDel(exec, pod, delegates[idx], rt, multusNetconf); err != nil {
errorstrings = append(errorstrings, err.Error())
}
if cniDeviceInfoPath != "" {
@@ -496,7 +480,7 @@ func delPlugins(exec invoke.Exec, pod *v1.Pod, args *skel.CmdArgs, k8sArgs *type
func cmdErr(k8sArgs *types.K8sArgs, format string, args ...interface{}) error {
prefix := "Multus: "
if k8sArgs != nil {
prefix += fmt.Sprintf("[%s/%s]: ", k8sArgs.K8S_POD_NAMESPACE, k8sArgs.K8S_POD_NAME)
prefix += fmt.Sprintf("[%s/%s/%s]: ", k8sArgs.K8S_POD_NAMESPACE, k8sArgs.K8S_POD_NAME, k8sArgs.K8S_POD_UID)
}
return logging.Errorf(prefix+format, args...)
}
@@ -504,7 +488,7 @@ func cmdErr(k8sArgs *types.K8sArgs, format string, args ...interface{}) error {
func cmdPluginErr(k8sArgs *types.K8sArgs, confName string, format string, args ...interface{}) error {
msg := ""
if k8sArgs != nil {
msg += fmt.Sprintf("[%s/%s:%s]: ", k8sArgs.K8S_POD_NAMESPACE, k8sArgs.K8S_POD_NAME, confName)
msg += fmt.Sprintf("[%s/%s/%s:%s]: ", k8sArgs.K8S_POD_NAMESPACE, k8sArgs.K8S_POD_NAME, k8sArgs.K8S_POD_UID, confName)
}
return logging.Errorf(msg+format, args...)
}
@@ -521,7 +505,78 @@ func isCriticalRequestRetriable(err error) bool {
return false
}
//CmdAdd ...
// GetPod retrieves Kubernetes Pod object from given namespace/name in k8sArgs (i.e. cni args)
// GetPod also get pod UID, but it is not used to retrieve, but it is used for double check
func GetPod(kubeClient *k8s.ClientInfo, k8sArgs *types.K8sArgs, isDel bool) (*v1.Pod, error) {
if kubeClient == nil {
return nil, nil
}
podNamespace := string(k8sArgs.K8S_POD_NAMESPACE)
podName := string(k8sArgs.K8S_POD_NAME)
podUID := string(k8sArgs.K8S_POD_UID)
// Keep track of how long getting the pod takes
logging.Debugf("GetPod for [%s/%s] starting", podNamespace, podName)
start := time.Now()
defer func() {
logging.Debugf("GetPod for [%s/%s] took %v", podNamespace, podName, time.Since(start))
}()
// Use a fairly long 0.25 sec interval so we don't hammer the apiserver
pollDuration := shortPollDuration
retryOnNotFound := func(error) bool {
return false
}
if kubeClient.PodInformer != nil {
logging.Debugf("GetPod for [%s/%s] will use informer cache", podNamespace, podName)
// Use short retry intervals with the informer since it's a local cache
pollDuration = informerPollDuration
// Retry NotFound on ADD since the cache may be a bit behind the apiserver
retryOnNotFound = func(e error) bool {
return !isDel && errors.IsNotFound(e)
}
}
var pod *v1.Pod
if err := wait.PollImmediate(pollDuration, shortPollTimeout, func() (bool, error) {
var getErr error
pod, getErr = kubeClient.GetPod(podNamespace, podName)
if isCriticalRequestRetriable(getErr) || retryOnNotFound(getErr) {
return false, nil
}
return pod != nil, getErr
}); err != nil {
if isDel && errors.IsNotFound(err) {
// On DEL pod may already be gone from apiserver/informer
return nil, nil
}
// Try one more time to get the pod directly from the apiserver;
// TODO: figure out why static pods don't show up via the informer
// and always hit this case.
pod, err = kubeClient.GetPod(podNamespace, podName)
if err != nil {
return nil, cmdErr(k8sArgs, "error waiting for pod: %v", err)
}
}
// In case of static pod, UID through kube api is different because of mirror pod, hence it is expected.
if podUID != "" && string(pod.UID) != podUID && !k8s.IsStaticPod(pod) {
msg := fmt.Sprintf("expected pod UID %q but got %q from Kube API", podUID, pod.UID)
if isDel {
// On CNI DEL we just operate on the cache when these mismatch, we don't error out.
// For example: stateful sets namespace/name can remain the same while podUID changes.
logging.Verbosef("warning: %s", msg)
return nil, nil
}
return nil, cmdErr(k8sArgs, msg)
}
return pod, nil
}
// CmdAdd ...
func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (cnitypes.Result, error) {
n, err := types.LoadNetConf(args.StdinData)
logging.Debugf("CmdAdd: %v, %v, %v", args, exec, kubeClient)
@@ -540,35 +595,14 @@ func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (c
}
if n.ReadinessIndicatorFile != "" {
err := wait.PollImmediate(pollDuration, pollTimeout, func() (bool, error) {
_, err := os.Stat(n.ReadinessIndicatorFile)
return err == nil, nil
})
if err != nil {
if err := types.GetReadinessIndicatorFile(n.ReadinessIndicatorFile); err != nil {
return nil, cmdErr(k8sArgs, "have you checked that your default network is ready? still waiting for readinessindicatorfile @ %v. pollimmediate error: %v", n.ReadinessIndicatorFile, err)
}
}
pod := (*v1.Pod)(nil)
if kubeClient != nil {
pod, err = kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
if err != nil {
var waitErr error
// in case of a retriable error, retry 10 times with 0.25 sec interval
if isCriticalRequestRetriable(err) {
waitErr = wait.PollImmediate(shortPollDuration, shortPollTimeout, func() (bool, error) {
pod, err = kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
return pod != nil, err
})
// retry failed, then return error with retry out
if waitErr != nil {
return nil, cmdErr(k8sArgs, "error getting pod with error: %v", err)
}
} else {
// Other case, return error
return nil, cmdErr(k8sArgs, "error getting pod: %v", err)
}
}
pod, err := GetPod(kubeClient, k8sArgs, false)
if err != nil {
return nil, err
}
// resourceMap holds Pod device allocation information; only initizized if CRD contains 'resourceName' annotation.
@@ -596,7 +630,6 @@ func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (c
var result, tmpResult cnitypes.Result
var netStatus []nettypes.NetworkStatus
cniArgs := os.Getenv("CNI_ARGS")
for idx, delegate := range n.Delegates {
ifName := getIfname(delegate, args.IfName, idx)
rt, cniDeviceInfoPath := types.CreateCNIRuntimeConf(args, k8sArgs, ifName, n.RuntimeConfig, delegate)
@@ -605,79 +638,118 @@ func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (c
// Even if the filename is set, file may not be present. Ignore error,
// but log and in the future may need to filter on specific errors.
if err != nil {
logging.Debugf("cmdAdd: CopyDeviceInfoForCNIFromDP returned an error - err=%v", err)
logging.Debugf("CmdAdd: CopyDeviceInfoForCNIFromDP returned an error - err=%v", err)
}
}
tmpResult, err = delegateAdd(exec, kubeClient, pod, ifName, delegate, rt, n, cniArgs)
// We collect the delegate netName for the cachefile name as well as following errors
netName := delegate.Conf.Name
if netName == "" {
netName = delegate.ConfList.Name
}
tmpResult, err = DelegateAdd(exec, kubeClient, pod, delegate, rt, n)
if err != nil {
// If the add failed, tear down all networks we already added
netName := delegate.Conf.Name
if netName == "" {
netName = delegate.ConfList.Name
}
// Ignore errors; DEL must be idempotent anyway
_ = delPlugins(exec, nil, args, k8sArgs, n.Delegates, idx, n.RuntimeConfig, n)
return nil, cmdPluginErr(k8sArgs, netName, "error adding container to network %q: %v", netName, err)
}
// Remove gateway from routing table if the gateway is not used
deletegateway := false
adddefaultgateway := false
if delegate.IsFilterGateway {
deletegateway = true
logging.Debugf("Marked interface %v for gateway deletion", ifName)
} else {
// Otherwise, determine if this interface now gets our default route.
// According to
// https://docs.google.com/document/d/1Ny03h6IDVy_e_vmElOqR7UdTPAG_RNydhVE1Kx54kFQ (4.1.2.1.9)
// the list can be empty; if it is, we'll assume the CNI's config for the default gateway holds,
// else we'll update the defaultgateway to the one specified.
if delegate.GatewayRequest != nil && delegate.GatewayRequest[0] != nil {
deletegateway = true
adddefaultgateway = true
logging.Debugf("Detected gateway override on interface %v to %v", ifName, delegate.GatewayRequest)
}
}
if deletegateway {
tmpResult, err = netutils.DeleteDefaultGW(args, ifName, &tmpResult)
if err != nil {
return nil, cmdErr(k8sArgs, "error deleting default gateway: %v", err)
}
}
// Here we'll set the default gateway
if adddefaultgateway {
tmpResult, err = netutils.SetDefaultGW(args, ifName, delegate.GatewayRequest, &tmpResult)
if err != nil {
return nil, cmdErr(k8sArgs, "error setting default gateway: %v", err)
}
}
// Master plugin result is always used if present
if delegate.MasterPlugin || result == nil {
result = tmpResult
}
res, err := cni100.NewResultFromResult(tmpResult)
if err != nil {
logging.Errorf("CmdAdd: failed to read result: %v, but proceed", err)
}
// check Interfaces and IPs because some CNI plugin does not create any interface
// and just returns empty result
if res != nil && (res.Interfaces != nil || res.IPs != nil) {
// Remove gateway from routing table if the gateway is not used
deleteV4gateway := false
deleteV6gateway := false
adddefaultgateway := false
if delegate.IsFilterV4Gateway {
deleteV4gateway = true
logging.Debugf("Marked interface %v for v4 gateway deletion", ifName)
} else {
// Otherwise, determine if this interface now gets our default route.
// According to
// https://docs.google.com/document/d/1Ny03h6IDVy_e_vmElOqR7UdTPAG_RNydhVE1Kx54kFQ (4.1.2.1.9)
// the list can be empty; if it is, we'll assume the CNI's config for the default gateway holds,
// else we'll update the defaultgateway to the one specified.
if delegate.GatewayRequest != nil && len(*delegate.GatewayRequest) != 0 {
deleteV4gateway = true
adddefaultgateway = true
logging.Debugf("Detected gateway override on interface %v to %v", ifName, delegate.GatewayRequest)
}
}
if delegate.IsFilterV6Gateway {
deleteV6gateway = true
logging.Debugf("Marked interface %v for v6 gateway deletion", ifName)
} else {
// Otherwise, determine if this interface now gets our default route.
// According to
// https://docs.google.com/document/d/1Ny03h6IDVy_e_vmElOqR7UdTPAG_RNydhVE1Kx54kFQ (4.1.2.1.9)
// the list can be empty; if it is, we'll assume the CNI's config for the default gateway holds,
// else we'll update the defaultgateway to the one specified.
if delegate.GatewayRequest != nil && len(*delegate.GatewayRequest) != 0 {
deleteV6gateway = true
adddefaultgateway = true
logging.Debugf("Detected gateway override on interface %v to %v", ifName, delegate.GatewayRequest)
}
}
// Remove gateway if `default-route` network selection is specified
if deleteV4gateway || deleteV6gateway {
err = netutils.DeleteDefaultGW(args.Netns, ifName)
if err != nil {
return nil, cmdErr(k8sArgs, "error deleting default gateway: %v", err)
}
err = netutils.DeleteDefaultGWCache(n.CNIDir, rt, netName, ifName, deleteV4gateway, deleteV6gateway)
if err != nil {
return nil, cmdErr(k8sArgs, "error deleting default gateway in cache: %v", err)
}
}
// Here we'll set the default gateway which specified in `default-route` network selection
if adddefaultgateway {
err = netutils.SetDefaultGW(args.Netns, ifName, *delegate.GatewayRequest)
if err != nil {
return nil, cmdErr(k8sArgs, "error setting default gateway: %v", err)
}
err = netutils.AddDefaultGWCache(n.CNIDir, rt, netName, ifName, *delegate.GatewayRequest)
if err != nil {
return nil, cmdErr(k8sArgs, "error setting default gateway in cache: %v", err)
}
}
}
// Read devInfo from CNIDeviceInfoFile if it exists so
// it can be copied to the NetworkStatus.
devinfo, err := getDelegateDeviceInfo(delegate, rt)
if err != nil {
// Even if the filename is set, file may not be present. Ignore error,
// but log and in the future may need to filter on specific errors.
logging.Debugf("cmdAdd: getDelegateDeviceInfo returned an error - err=%v", err)
logging.Debugf("CmdAdd: getDelegateDeviceInfo returned an error - err=%v", err)
}
// create the network status, only in case Multus as kubeconfig
if n.Kubeconfig != "" && kc != nil {
// Create the network statuses, only in case Multus has kubeconfig
if kubeClient != nil && kc != nil {
if !types.CheckSystemNamespaces(string(k8sArgs.K8S_POD_NAME), n.SystemNamespaces) {
delegateNetStatus, err := nadutils.CreateNetworkStatus(tmpResult, delegate.Name, delegate.MasterPlugin, devinfo)
delegateNetStatuses, err := nadutils.CreateNetworkStatuses(tmpResult, delegate.Name, delegate.MasterPlugin, devinfo)
if err != nil {
return nil, cmdErr(k8sArgs, "error setting network status: %v", err)
return nil, cmdErr(k8sArgs, "error setting network statuses: %v", err)
}
netStatus = append(netStatus, *delegateNetStatus)
// Append all returned statuses after dereferencing each
for _, status := range delegateNetStatuses {
netStatus = append(netStatus, *status)
}
}
} else if devinfo != nil {
// Warn that devinfo exists but could not add it to downwards API
@@ -686,7 +758,7 @@ func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (c
}
// set the network status annotation in apiserver, only in case Multus as kubeconfig
if n.Kubeconfig != "" && kc != nil {
if kubeClient != nil && kc != nil {
if !types.CheckSystemNamespaces(string(k8sArgs.K8S_POD_NAME), n.SystemNamespaces) {
err = k8s.SetNetworkStatus(kubeClient, k8sArgs, netStatus, n)
if err != nil {
@@ -701,7 +773,7 @@ func CmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) (c
return result, nil
}
//CmdCheck ...
// CmdCheck ...
func CmdCheck(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) error {
in, err := types.LoadNetConf(args.StdinData)
logging.Debugf("CmdCheck: %v, %v, %v", args, exec, kubeClient)
@@ -718,7 +790,7 @@ func CmdCheck(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo)
ifName := getIfname(delegate, args.IfName, idx)
rt, _ := types.CreateCNIRuntimeConf(args, k8sArgs, ifName, in.RuntimeConfig, delegate)
err = delegateCheck(exec, ifName, delegate, rt, in)
err = DelegateCheck(exec, delegate, rt, in)
if err != nil {
return err
}
@@ -727,7 +799,7 @@ func CmdCheck(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo)
return nil
}
//CmdDel ...
// CmdDel ...
func CmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) error {
in, err := types.LoadNetConf(args.StdinData)
logging.Debugf("CmdDel: %v, %v, %v", args, exec, kubeClient)
@@ -735,21 +807,7 @@ func CmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) er
return err
}
netnsfound := true
netns, err := ns.GetNS(args.Netns)
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
// https://github.com/kubernetes/kubernetes/issues/43014#issuecomment-287164444
_, ok := err.(ns.NSPathNotExistErr)
if ok {
netnsfound = false
logging.Debugf("CmdDel: WARNING netns may not exist, netns: %s, err: %s", args.Netns, err)
} else {
return cmdErr(nil, "failed to open netns %q: %v", netns, err)
}
}
if netns != nil {
defer netns.Close()
}
@@ -760,12 +818,13 @@ func CmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) er
}
if in.ReadinessIndicatorFile != "" {
err := wait.PollImmediate(pollDuration, pollTimeout, func() (bool, error) {
_, err := os.Stat(in.ReadinessIndicatorFile)
return err == nil, nil
})
readinessfileexists, err := types.ReadinessIndicatorExistsNow(in.ReadinessIndicatorFile)
if err != nil {
return cmdErr(k8sArgs, "PollImmediate error waiting for ReadinessIndicatorFile (on del): %v", err)
return cmdErr(k8sArgs, "error checking readinessindicatorfile on CNI DEL @ %v: %v", in.ReadinessIndicatorFile, err)
}
if !readinessfileexists {
logging.Verbosef("warning: readinessindicatorfile @ %v does not exist on CNI DEL", in.ReadinessIndicatorFile)
}
}
@@ -774,34 +833,33 @@ func CmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) er
return cmdErr(nil, "error getting k8s client: %v", err)
}
pod := (*v1.Pod)(nil)
if kubeClient != nil {
pod, err = kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
if err != nil {
var waitErr error
// in case of a retriable error, retry 10 times with 0.25 sec interval
if isCriticalRequestRetriable(err) {
waitErr = wait.PollImmediate(shortPollDuration, shortPollTimeout, func() (bool, error) {
pod, err = kubeClient.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
return pod != nil, err
})
// retry failed, then return error with retry out
if waitErr != nil {
return cmdErr(k8sArgs, "error getting pod with error: %v", err)
}
} else if errors.IsNotFound(err) {
// If not found, proceed to remove interface with cache
pod = nil
} else {
// Other case, return error
return cmdErr(k8sArgs, "error getting pod: %v", err)
}
}
pod, err := GetPod(kubeClient, k8sArgs, true)
if err != nil {
// GetPod may be failed but just do print error in its log and continue to delete
logging.Errorf("Multus: GetPod failed: %v, but continue to delete", err)
}
// Read the cache to get delegates json for the pod
netconfBytes, path, err := consumeScratchNetConf(args.ContainerID, in.CNIDir)
if err != nil {
useCacheConf := false
if err == nil {
in.Delegates = []*types.DelegateNetConf{}
if err := json.Unmarshal(netconfBytes, &in.Delegates); err != nil {
logging.Errorf("Multus: failed to load netconf: %v", err)
} else {
useCacheConf = true
// check plugins field and enable ConfListPlugin if there is
for _, v := range in.Delegates {
if len(v.ConfList.Plugins) != 0 {
v.ConfListPlugin = true
}
}
// First delegate is always the master plugin
in.Delegates[0].MasterPlugin = true
}
}
if !useCacheConf {
// Fetch delegates again if cache is not exist and pod info can be read
if os.IsNotExist(err) && pod != nil {
if in.ClusterNetwork != "" {
@@ -829,43 +887,41 @@ func CmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) er
logging.Errorf("Multus: failed to get the cached delegates file: %v, cannot properly delete", err)
return nil
}
} else {
defer os.Remove(path)
if err := json.Unmarshal(netconfBytes, &in.Delegates); err != nil {
return cmdErr(k8sArgs, "failed to load netconf: %v", err)
}
// check plugins field and enable ConfListPlugin if there is
for _, v := range in.Delegates {
if len(v.ConfList.Plugins) != 0 {
v.ConfListPlugin = true
}
}
// First delegate is always the master plugin
in.Delegates[0].MasterPlugin = true
}
// set CNIVersion in delegate CNI config if there is no CNIVersion and multus conf have CNIVersion.
for _, v := range in.Delegates {
if v.ConfListPlugin == true && v.ConfList.CNIVersion == "" && in.CNIVersion != "" {
if v.ConfListPlugin && v.ConfList.CNIVersion == "" && in.CNIVersion != "" {
v.ConfList.CNIVersion = in.CNIVersion
v.Bytes, err = json.Marshal(v.ConfList)
}
}
// unset the network status annotation in apiserver, only in case Multus as kubeconfig
if in.Kubeconfig != "" {
if netnsfound {
if !types.CheckSystemNamespaces(string(k8sArgs.K8S_POD_NAMESPACE), in.SystemNamespaces) {
err := k8s.SetNetworkStatus(kubeClient, k8sArgs, nil, in)
if err != nil {
// error happen but continue to delete
logging.Errorf("Multus: error unsetting the networks status: %v", err)
}
if err != nil {
// error happen but continue to delete
logging.Errorf("Multus: failed to marshal delegate %q config: %v", v.Name, err)
}
} else {
logging.Debugf("WARNING: Unset SetNetworkStatus skipped due to netns not found.")
}
}
return delPlugins(exec, pod, args, k8sArgs, in.Delegates, len(in.Delegates)-1, in.RuntimeConfig, in)
e := delPlugins(exec, pod, args, k8sArgs, in.Delegates, len(in.Delegates)-1, in.RuntimeConfig, in)
// Enable Option only delegate plugin delete success to delete cache file
// CNI Runtime maybe return an error to block sandbox cleanup a while initiative,
// like starting, prepare something, it will be OK when retry later
// put "delete cache file" off later ensure have enough info delegate DEL message when Pod has been fully
// deleted from ETCD before sandbox cleanup success..
if in.RetryDeleteOnError {
if useCacheConf {
// Kubelet though this error as has been cleanup success and never retry, clean cache also
// Block sandbox cleanup error message can not contain "no such file or directory", CNI Runtime maybe should adaptor it !
if e == nil || strings.Contains(e.Error(), "no such file or directory") {
_ = os.Remove(path) // lgtm[go/path-injection]
}
}
} else {
if useCacheConf {
// remove used cache file
_ = os.Remove(path) // lgtm[go/path-injection]
}
}
return e
}

View File

@@ -0,0 +1,834 @@
// Copyright (c) 2022 Multus 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 multus
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"context"
"fmt"
"os"
"reflect"
"github.com/containernetworking/cni/pkg/skel"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/k8sclient"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
testhelpers "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/testing"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("multus operations", func() {
It("fails to save NetConf with bad filepath", func() {
meme := []byte(`meme`)
err := saveScratchNetConf("123456789", "", meme)
Expect(err).To(HaveOccurred())
})
It("fails to delete delegates with bad filepath", func() {
err := deleteDelegates("123456789", "bad!file!~?Path$^")
Expect(err).To(HaveOccurred())
})
It("delete delegates given good filepath", func() {
os.MkdirAll("/opt/cni/bin", 0755)
d1 := []byte("blah")
os.WriteFile("/opt/cni/bin/123456789", d1, 0644)
err := deleteDelegates("123456789", "/opt/cni/bin")
Expect(err).NotTo(HaveOccurred())
})
})
var _ = Describe("multus operations cniVersion 0.2.0 config", func() {
var testNS ns.NetNS
var tmpDir string
configPath := "/tmp/foo.multus.conf"
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
testNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
os.Setenv("CNI_NETNS", testNS.Path())
os.Setenv("CNI_PATH", "/some/path")
tmpDir, err = os.MkdirTemp("", "multus_tmp")
Expect(err).NotTo(HaveOccurred())
// Touch the default network file.
os.OpenFile(configPath, os.O_RDONLY|os.O_CREATE, 0755)
})
AfterEach(func() {
// Cleanup default network file.
if _, errStat := os.Stat(configPath); errStat == nil {
errRemove := os.Remove(configPath)
Expect(errRemove).NotTo(HaveOccurred())
}
Expect(testNS.Close()).To(Succeed())
os.Unsetenv("CNI_PATH")
os.Unsetenv("CNI_ARGS")
err := os.RemoveAll(tmpDir)
Expect(err).NotTo(HaveOccurred())
})
It("executes delegates", func() {
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
},{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}]
}`),
}
logging.SetLogLevel("verbose")
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
expectedResult2 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.5/24"),
},
}
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
fExec.addPlugin020(nil, "net1", expectedConf2, expectedResult2, nil)
result, err := CmdAdd(args, fExec, nil)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
err = CmdDel(args, fExec, nil)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("executes delegates (plugin without interface)", func() {
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
},{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}]
}`),
}
logging.SetLogLevel("verbose")
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
// other1 just returns empty result
expectedResult2 := &types020.Result{
CNIVersion: "0.2.0",
}
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
fExec.addPlugin020(nil, "net1", expectedConf2, expectedResult2, nil)
result, err := CmdAdd(args, fExec, nil)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
err = CmdDel(args, fExec, nil)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("executes delegates given faulty namespace", func() {
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: "fsdadfad",
IfName: "eth0",
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
},{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}]
}`),
}
// Netns is given garbage value
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
expectedResult2 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.5/24"),
},
}
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
fExec.addPlugin020(nil, "net1", expectedConf2, expectedResult2, nil)
_, err := CmdAdd(args, fExec, nil)
Expect(err).To(MatchError("[//:weave1]: error adding container to network \"weave1\": DelegateAdd: cannot set \"weave-net\" interface name to \"eth0\": validateIfName: no net namespace fsdadfad found: failed to Statfs \"fsdadfad\": no such file or directory"))
})
It("fails to load NetConf with bad json in CmdAdd/Del", func() {
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
},{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}]
`),
}
// Missing close bracket in StdinData
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
expectedResult2 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.5/24"),
},
}
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
fExec.addPlugin020(nil, "net1", expectedConf2, expectedResult2, nil)
_, err := CmdAdd(args, fExec, nil)
Expect(err).To(HaveOccurred())
err = CmdDel(args, fExec, nil)
Expect(err).To(HaveOccurred())
})
It("executes delegates and cleans up on failure", func() {
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [%s,%s]
}`, expectedConf1, expectedConf2)),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
// This plugin invocation should fail
err := fmt.Errorf("expected plugin failure")
fExec.addPlugin020(nil, "net1", expectedConf2, nil, err)
_, err = CmdAdd(args, fExec, nil)
Expect(fExec.addIndex).To(Equal(2))
Expect(fExec.delIndex).To(Equal(2))
Expect(err).To(MatchError("[//:other1]: error adding container to network \"other1\": expected plugin failure"))
})
It("executes delegates and cleans up on failure with missing name field", func() {
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
expectedConf2 := `{
"name": "",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
// took out the name in expectedConf2, expecting a new value to be filled in by CmdAdd
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [%s,%s]
}`, expectedConf1, expectedConf2)),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
// This plugin invocation should fail
err := fmt.Errorf("expected plugin failure")
fExec.addPlugin020(nil, "net1", expectedConf2, nil, err)
_, err = CmdAdd(args, fExec, nil)
Expect(fExec.addIndex).To(Equal(1))
Expect(fExec.delIndex).To(Equal(2))
Expect(err).To(HaveOccurred())
})
It("executes delegates and kubernetes networks with events check", func() {
fakePod := testhelpers.NewFakePod("testpod", "net1,net2", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
net2 := `{
"name": "net2",
"type": "mynet2",
"cniVersion": "0.2.0"
}`
net3 := `{
"name": "net3",
"type": "mynet3",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin020(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fExec.addPlugin020(nil, "net2", net2, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.4/24"),
},
}, nil)
clientInfo := NewFakeClientInfo()
_, err := clientInfo.Client.CoreV1().Pods(fakePod.ObjectMeta.Namespace).Create(
context.TODO(), fakePod, metav1.CreateOptions{})
Expect(err).NotTo(HaveOccurred())
_, err = clientInfo.AddNetAttachDef(
testhelpers.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net1", net1))
Expect(err).NotTo(HaveOccurred())
_, err = clientInfo.AddNetAttachDef(
testhelpers.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net2", net2))
Expect(err).NotTo(HaveOccurred())
// net3 is not used; make sure it's not accessed
_, err = clientInfo.AddNetAttachDef(
testhelpers.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net3", net3))
Expect(err).NotTo(HaveOccurred())
result, err := CmdAdd(args, fExec, clientInfo)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
recorder := clientInfo.EventRecorder.(*record.FakeRecorder)
events := collectEvents(recorder.Events)
Expect(len(events)).To(Equal(3))
Expect(events[0]).To(Equal("Normal AddedInterface Add eth0 [1.1.1.2/24] from weave1"))
Expect(events[1]).To(Equal("Normal AddedInterface Add net1 [1.1.1.3/24] from test/net1"))
Expect(events[2]).To(Equal("Normal AddedInterface Add net2 [1.1.1.4/24] from test/net2"))
})
It("executes kubernetes networks and delete it after pod removal", func() {
fakePod := testhelpers.NewFakePod("testpod", "net1", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin020(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fKubeClient := NewFakeClientInfo()
_, err := fKubeClient.AddPod(fakePod)
Expect(err).NotTo(HaveOccurred())
_, err = fKubeClient.AddNetAttachDef(
testhelpers.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net1", net1))
Expect(err).NotTo(HaveOccurred())
result, err := CmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
// set fKubeClient to nil to emulate no pod info
err = fKubeClient.DeletePod(fakePod.ObjectMeta.Namespace, fakePod.ObjectMeta.Name)
Expect(err).NotTo(HaveOccurred())
err = CmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("executes clusterNetwork delegate", func() {
fakePod := testhelpers.NewFakePod("testpod", "", "kube-system/net1")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"defaultNetworks": [],
"clusterNetwork": "net1",
"delegates": []
}`),
}
fExec := newFakeExec()
fExec.addPlugin020(nil, "eth0", net1, expectedResult1, nil)
fKubeClient := NewFakeClientInfo()
fKubeClient.AddPod(fakePod)
_, err := fKubeClient.AddNetAttachDef(testhelpers.NewFakeNetAttachDef("kube-system", "net1", net1))
Expect(err).NotTo(HaveOccurred())
result, err := CmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
err = CmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("Verify the cache is created in dataDir", func() {
tmpCNIDir := tmpDir + "/cniData"
err := os.Mkdir(tmpCNIDir, 0777)
Expect(err).NotTo(HaveOccurred())
fakePod := testhelpers.NewFakePod("testpod", "net1", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"cniDir": "%s",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`, tmpCNIDir)),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin020(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fKubeClient := NewFakeClientInfo()
fKubeClient.AddPod(fakePod)
_, err = fKubeClient.AddNetAttachDef(
testhelpers.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net1", net1))
Expect(err).NotTo(HaveOccurred())
result, err := CmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
By("Verify cache file existence")
cacheFilePath := fmt.Sprintf("%s/%s", tmpCNIDir, "123456789")
_, err = os.Stat(cacheFilePath)
Expect(err).NotTo(HaveOccurred())
By("Delete and check net count is not incremented")
err = CmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("Delete pod without cache", func() {
tmpCNIDir := tmpDir + "/cniData"
err := os.Mkdir(tmpCNIDir, 0777)
Expect(err).NotTo(HaveOccurred())
fakePod := testhelpers.NewFakePod("testpod", "net1", "")
net1 := `{
"name": "net1",
"type": "mynet",
"cniVersion": "0.2.0"
}`
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
Args: fmt.Sprintf("K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s", fakePod.ObjectMeta.Name, fakePod.ObjectMeta.Namespace),
StdinData: []byte(fmt.Sprintf(`{
"name": "node-cni-network",
"type": "multus",
"kubeconfig": "/etc/kubernetes/node-kubeconfig.yaml",
"cniDir": "%s",
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}]
}`, tmpCNIDir)),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
fExec.addPlugin020(nil, "net1", net1, &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.3/24"),
},
}, nil)
fKubeClient := NewFakeClientInfo()
fKubeClient.AddPod(fakePod)
_, err = fKubeClient.AddNetAttachDef(
testhelpers.NewFakeNetAttachDef(fakePod.ObjectMeta.Namespace, "net1", net1))
Expect(err).NotTo(HaveOccurred())
result, err := CmdAdd(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.addIndex).To(Equal(len(fExec.plugins)))
r := result.(*types020.Result)
// plugin 1 is the masterplugin
Expect(reflect.DeepEqual(r, expectedResult1)).To(BeTrue())
By("Verify cache file existence")
cacheFilePath := fmt.Sprintf("%s/%s", tmpCNIDir, "123456789")
_, err = os.Stat(cacheFilePath)
Expect(err).NotTo(HaveOccurred())
err = os.Remove(cacheFilePath)
Expect(err).NotTo(HaveOccurred())
By("Delete and check pod/net count is incremented")
err = CmdDel(args, fExec, fKubeClient)
Expect(err).NotTo(HaveOccurred())
Expect(fExec.delIndex).To(Equal(len(fExec.plugins)))
})
It("fails to execute confListDel given no 'plugins' key", func() {
args := &skel.CmdArgs{
ContainerID: "123456789",
Netns: testNS.Path(),
IfName: "eth0",
StdinData: []byte(`{
"name": "node-cni-network",
"type": "multus",
"readinessindicatorfile": "/tmp/foo.multus.conf",
"defaultnetworkwaitseconds": 3,
"delegates": [{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
},{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}]
}`),
}
fExec := newFakeExec()
expectedResult1 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.2/24"),
},
}
expectedConf1 := `{
"name": "weave1",
"cniVersion": "0.2.0",
"type": "weave-net"
}`
fExec.addPlugin020(nil, "eth0", expectedConf1, expectedResult1, nil)
expectedResult2 := &types020.Result{
CNIVersion: "0.2.0",
IP4: &types020.IPConfig{
IP: *testhelpers.EnsureCIDR("1.1.1.5/24"),
},
}
expectedConf2 := `{
"name": "other1",
"cniVersion": "0.2.0",
"type": "other-plugin"
}`
fExec.addPlugin020(nil, "net1", expectedConf2, expectedResult2, nil)
fakeMultusNetConf := types.NetConf{
BinDir: "/opt/cni/bin",
}
// use fExec for the exec param
rawnetconflist := []byte(`{"cniVersion":"0.2.0","name":"weave1","type":"weave-net"}`)
k8sargs, err := k8sclient.GetK8sArgs(args)
n, err := types.LoadNetConf(args.StdinData)
rt, _ := types.CreateCNIRuntimeConf(args, k8sargs, args.IfName, n.RuntimeConfig, nil)
err = conflistDel(rt, rawnetconflist, &fakeMultusNetConf, fExec)
Expect(err).To(HaveOccurred())
})
})

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
// Copyright (c) 2022 Multus 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 multus
// disable dot-imports only for testing
//revive:disable:dot-imports
import (
"bytes"
"context"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"testing"
cnitypes "github.com/containernetworking/cni/pkg/types"
cni020 "github.com/containernetworking/cni/pkg/types/020"
cni040 "github.com/containernetworking/cni/pkg/types/040"
cni100 "github.com/containernetworking/cni/pkg/types/100"
cniversion "github.com/containernetworking/cni/pkg/version"
netfake "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned/fake"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/k8sclient"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestMultus(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "multus")
}
type fakePlugin struct {
expectedEnv []string
expectedConf string
expectedIfname string
result cnitypes.Result
err error
}
type fakeExec struct {
cniversion.PluginDecoder
addIndex int
delIndex int
chkIndex int
expectedDelSkip int
plugins map[string]*fakePlugin
}
func newFakeExec() *fakeExec {
return &fakeExec{
plugins: map[string]*fakePlugin{},
}
}
func (f *fakeExec) addPlugin100(expectedEnv []string, expectedIfname, expectedConf string, result *cni100.Result, err error) {
f.plugins[expectedIfname] = &fakePlugin{
expectedEnv: expectedEnv,
expectedConf: expectedConf,
expectedIfname: expectedIfname,
result: result,
err: err,
}
if err != nil && err.Error() == "missing network name" {
f.expectedDelSkip++
}
}
func (f *fakeExec) addPlugin040(expectedEnv []string, expectedIfname, expectedConf string, result *cni040.Result, err error) {
f.plugins[expectedIfname] = &fakePlugin{
expectedEnv: expectedEnv,
expectedConf: expectedConf,
expectedIfname: expectedIfname,
result: result,
err: err,
}
if err != nil && err.Error() == "missing network name" {
f.expectedDelSkip++
}
}
func (f *fakeExec) addPlugin020(expectedEnv []string, expectedIfname, expectedConf string, result *cni020.Result, err error) {
f.plugins[expectedIfname] = &fakePlugin{
expectedEnv: expectedEnv,
expectedConf: expectedConf,
expectedIfname: expectedIfname,
result: result,
err: err,
}
if err != nil && err.Error() == "missing network name" {
f.expectedDelSkip++
}
}
func matchArray(a1, a2 []string) {
Expect(len(a1)).To(Equal(len(a2)))
for _, e1 := range a1 {
found := ""
for _, e2 := range a2 {
if e1 == e2 {
found = e2
break
}
}
// Compare element values for more descriptive test failure
Expect(e1).To(Equal(found))
}
}
// When faking plugin execution the ExecPlugin() call environ is not populated
// (while it would be for real exec). Filter the environment variables for
// CNI-specific ones that testcases will care about.
func gatherCNIEnv(environ []string) []string {
filtered := make([]string, 0)
for _, v := range environ {
if strings.HasPrefix(v, "CNI_") {
filtered = append(filtered, v)
}
}
return filtered
}
func ParseEnvironment(environ []string) map[string]string {
m := map[string]string{}
for _, e := range environ {
if e != "" {
parts := strings.SplitN(e, "=", 2)
ExpectWithOffset(2, len(parts)).To(Equal(2))
m[parts[0]] = parts[1]
}
}
return m
}
func (f *fakeExec) ExecPlugin(_ context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
envMap := ParseEnvironment(environ)
cmd := envMap["CNI_COMMAND"]
var index int
var err error
var resultJSON []byte
switch cmd {
case "ADD":
Expect(len(f.plugins)).To(BeNumerically(">", f.addIndex))
index = f.addIndex
f.addIndex++
case "CHECK":
Expect(len(f.plugins)).To(BeNumerically("==", f.addIndex))
index = f.chkIndex
f.chkIndex++
case "DEL":
Expect(len(f.plugins)).To(BeNumerically(">", f.delIndex))
index = len(f.plugins) - f.expectedDelSkip - f.delIndex - 1
f.delIndex++
default:
// Should never be reached
Expect(false).To(BeTrue())
}
plugin := f.plugins[envMap["CNI_IFNAME"]]
//GinkgoT().Logf("[%s %d] exec plugin %q found %+v\n", cmd, index, pluginPath, plugin)
fmt.Printf("[%s %d] exec plugin %q found %+v\n", cmd, index, pluginPath, plugin)
// strip prevResult from stdinData; tests don't need it
var m map[string]interface{}
reader := strings.NewReader(string(stdinData))
writer := new(bytes.Buffer)
dec := json.NewDecoder(reader)
enc := json.NewEncoder(writer)
err = dec.Decode(&m)
Expect(err).NotTo(HaveOccurred())
for k := range m {
if k == "prevResult" {
delete(m, k)
}
}
err = enc.Encode(&m)
Expect(err).NotTo(HaveOccurred())
if plugin.expectedConf != "" {
Expect(writer).To(MatchJSON(plugin.expectedConf))
}
if plugin.expectedIfname != "" {
Expect(envMap["CNI_IFNAME"]).To(Equal(plugin.expectedIfname))
}
if len(plugin.expectedEnv) > 0 {
cniEnv := gatherCNIEnv(environ)
for _, expectedCniEnvVar := range plugin.expectedEnv {
Expect(cniEnv).To(ContainElement(expectedCniEnvVar))
}
}
if plugin.err != nil {
return nil, plugin.err
}
resultJSON, err = json.Marshal(plugin.result)
Expect(err).NotTo(HaveOccurred())
return resultJSON, nil
}
func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) {
Expect(len(paths)).To(BeNumerically(">", 0))
return filepath.Join(paths[0], plugin), nil
}
// NewFakeClientInfo returns fake client (just for testing)
func NewFakeClientInfo() *k8sclient.ClientInfo {
return &k8sclient.ClientInfo{
Client: fake.NewSimpleClientset(),
NetClient: netfake.NewSimpleClientset(),
EventRecorder: record.NewFakeRecorder(10),
}
}
func collectEvents(source <-chan string) []string {
done := false
events := make([]string, 0)
for !done {
select {
case ev := <-source:
events = append(events, ev)
default:
done = true
}
}
return events
}

File diff suppressed because it is too large Load Diff

16
pkg/netutils/doc.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2022 Multus 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 netutils is the package that contains network related utilities.
package netutils

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2019 Multus Authors
// Copyright (c) 2019 Intel Corporation
// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,31 +12,27 @@
// 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 netutils
import (
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"github.com/containernetworking/cni/libcni"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/vishvananda/netlink"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
"net"
"strings"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
)
// DeleteDefaultGW removes the default gateway from marked interfaces.
func DeleteDefaultGW(args *skel.CmdArgs, ifName string, res *cnitypes.Result) (*current.Result, error) {
result, err := current.NewResultFromResult(*res)
func DeleteDefaultGW(netnsPath string, ifName string) error {
netns, err := ns.GetNS(netnsPath)
if err != nil {
return nil, logging.Errorf("DeleteDefaultGW: Error creating new from current CNI result: %v", err)
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return nil, logging.Errorf("DeleteDefaultGW: Error getting namespace %v", err)
return logging.Errorf("DeleteDefaultGW: Error getting namespace %v", err)
}
defer netns.Close()
@@ -50,40 +47,27 @@ func DeleteDefaultGW(args *skel.CmdArgs, ifName string, res *cnitypes.Result) (*
}
return err
})
var newRoutes []*cnitypes.Route
for _, route := range result.Routes {
if mask, _ := route.Dst.Mask.Size(); mask != 0 {
newRoutes = append(newRoutes, route)
}
}
result.Routes = newRoutes
return result, err
return err
}
// SetDefaultGW adds a default gateway on a specific interface
func SetDefaultGW(args *skel.CmdArgs, ifName string, gateways []net.IP, res *cnitypes.Result) (*current.Result, error) {
// Use the current CNI result...
result, err := current.NewResultFromResult(*res)
if err != nil {
return nil, logging.Errorf("SetDefaultGW: Error creating new CNI result from current: %v", err)
}
func SetDefaultGW(netnsPath string, ifName string, gateways []net.IP) error {
// This ensures we're acting within the net namespace for the pod.
netns, err := ns.GetNS(args.Netns)
netns, err := ns.GetNS(netnsPath)
if err != nil {
return nil, logging.Errorf("SetDefaultGW: Error getting namespace %v", err)
return logging.Errorf("SetDefaultGW: Error getting namespace %v", err)
}
defer netns.Close()
var newResultDefaultRoutes []*cnitypes.Route
// Do this within the net namespace.
err = netns.Do(func(_ ns.NetNS) error {
var err error
// Pick up the link info as we need the index.
link, _ := netlink.LinkByName(ifName)
link, err := netlink.LinkByName(ifName)
if err != nil {
return logging.Errorf("SetDefaultGW: Error getting link %v", err)
}
// Cycle through all the desired gateways.
for _, gw := range gateways {
@@ -95,15 +79,6 @@ func SetDefaultGW(args *skel.CmdArgs, ifName string, gateways []net.IP, res *cni
Gw: gw,
}
// Build a new element for the results route
// Set a correct CIDR depending on IP type
_, dstipnet, _ := net.ParseCIDR("::0/0")
if strings.Count(gw.String(), ":") < 2 {
_, dstipnet, _ = net.ParseCIDR("0.0.0.0/0")
}
newResultDefaultRoutes = append(newResultDefaultRoutes, &cnitypes.Route{Dst: *dstipnet, GW: gw})
// Perform the creation of the default route....
err = netlink.RouteAdd(&newDefaultRoute)
if err != nil {
@@ -113,7 +88,323 @@ func SetDefaultGW(args *skel.CmdArgs, ifName string, gateways []net.IP, res *cni
return err
})
result.Routes = newResultDefaultRoutes
return result, err
return err
}
// DeleteDefaultGWCache updates libcni cache to remove default gateway routes in result
func DeleteDefaultGWCache(cacheDir string, rt *libcni.RuntimeConf, netName string, _ string, ipv4, ipv6 bool) error {
cacheFile := filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName))
cache, err := os.ReadFile(cacheFile)
if err != nil {
return err
}
logging.Debugf("DeleteDefaultGWCache: update cache to delete GW from: %s", string(cache))
newCache, err := deleteDefaultGWCacheBytes(cache, ipv4, ipv6)
if err != nil {
return err
}
logging.Debugf("DeleteDefaultGWCache: update cache to delete GW: %s", string(newCache))
return os.WriteFile(cacheFile, newCache, 0600)
}
func deleteDefaultGWCacheBytes(cacheFile []byte, ipv4, ipv6 bool) ([]byte, error) {
var cachedInfo map[string]interface{}
if err := json.Unmarshal(cacheFile, &cachedInfo); err != nil {
return nil, err
}
// try to get result
_, ok := cachedInfo["result"]
if !ok {
return nil, fmt.Errorf("cannot get result from cache")
}
resultJSON, ok := cachedInfo["result"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong result type: %v", cachedInfo["result"])
}
newResult, err := deleteDefaultGWResult(resultJSON, ipv4, ipv6)
if err != nil {
return nil, err
}
cachedInfo["result"] = newResult
newCache, err := json.Marshal(cachedInfo)
if err != nil {
return nil, fmt.Errorf("failed to encode json: %v", err)
}
return newCache, nil
}
func deleteDefaultGWResultRoutes(routes []interface{}, dstGW string) ([]interface{}, error) {
var newRoutes []interface{}
for i, r := range routes {
route, ok := r.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong route format: %v", r)
}
_, ok = route["dst"]
if ok {
dst, ok := route["dst"].(string)
if !ok {
return nil, fmt.Errorf("wrong dst format: %v", route["dst"])
}
if dst != dstGW {
newRoutes = append(newRoutes, routes[i])
}
}
}
return newRoutes, nil
}
func deleteDefaultGWResult(result map[string]interface{}, ipv4, ipv6 bool) (map[string]interface{}, error) {
// try to get cniVersion from result
_, ok := result["cniVersion"]
if !ok {
// fallback to processing result for old cni version(0.1.0/0.2.0)
return deleteDefaultGWResult020(result, ipv4, ipv6)
}
cniVersion, ok := result["cniVersion"].(string)
if !ok {
return nil, fmt.Errorf("wrong cniVersion format: %v", result["cniVersion"])
}
if cniVersion == "0.1.0" || cniVersion == "0.2.0" {
// fallback to processing result for old cni version(0.1.0/0.2.0)
return deleteDefaultGWResult020(result, ipv4, ipv6)
}
if cniVersion != "0.3.0" && cniVersion != "0.3.1" && cniVersion != "0.4.0" && cniVersion != "1.0.0" {
return nil, fmt.Errorf("not supported version: %s", cniVersion)
}
_, ok = result["routes"]
if !ok {
// No route in result, hence we do nothing
return result, nil
}
routes, ok := result["routes"].([]interface{})
if !ok {
return nil, fmt.Errorf("wrong routes format: %v", result["routes"])
}
var err error
// delete IPv4 default routes
if ipv4 {
routes, err = deleteDefaultGWResultRoutes(routes, "0.0.0.0/0")
if err != nil {
return nil, err
}
}
if ipv6 {
routes, err = deleteDefaultGWResultRoutes(routes, "::0/0")
if err != nil {
return nil, err
}
}
if len(routes) == 0 {
delete(result, "routes")
} else {
result["routes"] = routes
}
return result, nil
}
func deleteDefaultGWResult020(result map[string]interface{}, ipv4, ipv6 bool) (map[string]interface{}, error) {
var err error
if ipv4 {
_, ok := result["ip4"]
if ok {
ip4, ok := result["ip4"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip4 format: %v", result["ip4"])
}
_, ok = ip4["routes"]
if ok {
routes, ok := ip4["routes"].([]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip4 routes format: %v", ip4["routes"])
}
routes, err = deleteDefaultGWResultRoutes(routes, "0.0.0.0/0")
if err != nil {
return nil, err
}
ip4["routes"] = routes
}
}
}
if ipv6 {
_, ok := result["ip6"]
if ok {
ip6, ok := result["ip6"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip6 format: %v", result["ip6"])
}
_, ok = ip6["routes"]
if ok {
routes, ok := ip6["routes"].([]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip6 routes format: %v", ip6["routes"])
}
routes, err = deleteDefaultGWResultRoutes(routes, "::0/0")
if err != nil {
return nil, err
}
ip6["routes"] = routes
}
}
}
return result, nil
}
// AddDefaultGWCache updates libcni cache to add default gateway result
func AddDefaultGWCache(cacheDir string, rt *libcni.RuntimeConf, netName string, _ string, gw []net.IP) error {
cacheFile := filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName))
cache, err := os.ReadFile(cacheFile)
if err != nil {
return err
}
logging.Debugf("AddDefaultGWCache: update cache to add GW from: %s", string(cache))
newCache, err := addDefaultGWCacheBytes(cache, gw)
if err != nil {
return err
}
logging.Debugf("AddDefaultGWCache: update cache to add GW: %s", string(newCache))
return os.WriteFile(cacheFile, newCache, 0600)
}
func addDefaultGWCacheBytes(cacheFile []byte, gw []net.IP) ([]byte, error) {
var cachedInfo map[string]interface{}
if err := json.Unmarshal(cacheFile, &cachedInfo); err != nil {
return nil, err
}
// try to get result
_, ok := cachedInfo["result"]
if !ok {
return nil, fmt.Errorf("cannot get result from cache")
}
resultJSON, ok := cachedInfo["result"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong result type: %v", cachedInfo["result"])
}
newResult, err := addDefaultGWResult(resultJSON, gw)
if err != nil {
return nil, err
}
cachedInfo["result"] = newResult
newCache, err := json.Marshal(cachedInfo)
if err != nil {
return nil, fmt.Errorf("failed to encode json: %v", err)
}
return newCache, nil
}
func addDefaultGWResult(result map[string]interface{}, gw []net.IP) (map[string]interface{}, error) {
// try to get cniVersion from result
_, ok := result["cniVersion"]
if !ok {
// fallback to processing result for old cni version(0.1.0/0.2.0)
return addDefaultGWResult020(result, gw)
}
cniVersion, ok := result["cniVersion"].(string)
if !ok {
return nil, fmt.Errorf("wrong cniVersion format: %v", result["cniVersion"])
}
if cniVersion == "0.1.0" || cniVersion == "0.2.0" {
// fallback to processing result for old cni version(0.1.0/0.2.0)
return addDefaultGWResult020(result, gw)
}
if cniVersion != "0.3.0" && cniVersion != "0.3.1" && cniVersion != "0.4.0" && cniVersion != "1.0.0" {
return nil, fmt.Errorf("not supported version: %s", cniVersion)
}
routes := []interface{}{}
_, ok = result["routes"]
if ok {
routes, ok = result["routes"].([]interface{})
if !ok {
return nil, fmt.Errorf("wrong routes format: %v", result["routes"])
}
}
for _, g := range gw {
dst := "0.0.0.0/0"
if g.To4() == nil {
dst = "::0/0"
}
routes = append(routes, map[string]string{
"dst": dst,
"gw": g.String(),
})
}
result["routes"] = routes
return result, nil
}
func addDefaultGWResult020(result map[string]interface{}, gw []net.IP) (map[string]interface{}, error) {
for _, g := range gw {
if g.To4() != nil {
_, ok := result["ip4"]
if ok {
ip4, ok := result["ip4"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip4 format: %v", result["ip4"])
}
routes := []interface{}{}
_, ok = ip4["routes"]
if ok {
routes, ok = ip4["routes"].([]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip4 routes format: %v", ip4["routes"])
}
}
ip4["routes"] = append(routes, map[string]string{
"dst": "0.0.0.0/0",
"gw": g.String(),
})
}
} else {
_, ok := result["ip6"]
if ok {
ip6, ok := result["ip6"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip6 format: %v", result["ip4"])
}
routes := []interface{}{}
_, ok = ip6["routes"]
if ok {
routes, ok = ip6["routes"].([]interface{})
if !ok {
return nil, fmt.Errorf("wrong ip6 routes format: %v", ip6["routes"])
}
}
ip6["routes"] = append(routes, map[string]string{
"dst": "::/0",
"gw": g.String(),
})
}
}
}
return result, nil
}

File diff suppressed because it is too large Load Diff

115
pkg/server/api/api.go Normal file
View File

@@ -0,0 +1,115 @@
// Copyright (c) 2022 Multus 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 api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
utilwait "k8s.io/apimachinery/pkg/util/wait"
)
const (
// APIReadyPollDuration specifies duration for API readiness check polling
APIReadyPollDuration = 100 * time.Millisecond
// APIReadyPollTimeout specifies timeout for API readiness check polling
APIReadyPollTimeout = 60000 * time.Millisecond
// MultusCNIAPIEndpoint is an endpoint for multus CNI request (for multus-shim)
MultusCNIAPIEndpoint = "/cni"
// MultusDelegateAPIEndpoint is an endpoint for multus delegate request (for hotplug)
MultusDelegateAPIEndpoint = "/delegate"
defaultMultusRunDir = "/run/multus/"
// MultusHealthAPIEndpoint is an endpoint API clients can query to know if they can communicate w/ multus server
MultusHealthAPIEndpoint = "/healthz"
)
// DoCNI sends a CNI request to the CNI server via JSON + HTTP over a root-owned unix socket,
// and returns the result
func DoCNI(url string, req interface{}, socketPath string) ([]byte, error) {
data, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal CNI request %v: %v", req, err)
}
client := &http.Client{
Transport: &http.Transport{
Dial: func(_, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
}
resp, err := client.Post(url, "application/json", bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("failed to send CNI request: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read CNI result: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("CNI request failed with status %v: '%s'", resp.StatusCode, string(body))
}
return body, nil
}
// GetAPIEndpoint returns endpoint URL for multus-daemon
func GetAPIEndpoint(endpoint string) string {
return fmt.Sprintf("http://dummy%s", endpoint)
}
// CreateDelegateRequest creates Request for delegate API request
func CreateDelegateRequest(cniCommand, cniContainerID, cniNetNS, cniIFName, podNamespace, podName, podUID string, cniConfig []byte, interfaceAttributes *DelegateInterfaceAttributes) *Request {
return &Request{
Env: map[string]string{
"CNI_COMMAND": strings.ToUpper(cniCommand),
"CNI_CONTAINERID": cniContainerID,
"CNI_NETNS": cniNetNS,
"CNI_IFNAME": cniIFName,
"CNI_ARGS": fmt.Sprintf("K8S_POD_NAMESPACE=%s;K8S_POD_NAME=%s;K8S_POD_UID=%s", podNamespace, podName, podUID),
},
Config: cniConfig,
InterfaceAttributes: interfaceAttributes,
}
}
// WaitUntilAPIReady checks API readiness
func WaitUntilAPIReady(socketPath string) error {
return utilwait.PollImmediate(APIReadyPollDuration, APIReadyPollTimeout, func() (bool, error) {
_, err := DoCNI(GetAPIEndpoint(MultusHealthAPIEndpoint), nil, SocketPath(socketPath))
return err == nil, nil
})
}
// CheckAPIReadyNow checks API readiness once
func CheckAPIReadyNow(socketPath string) error {
_, err := DoCNI(GetAPIEndpoint(MultusHealthAPIEndpoint), nil, SocketPath(socketPath))
if err != nil {
return fmt.Errorf("CheckAPIReadyNow: Daemon not reachable over socketfile: %v", err)
}
return nil
}

16
pkg/server/api/doc.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright (c) 2022 Multus 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 api is the package that contains thick pluigin's server apis.
package api

143
pkg/server/api/shim.go Normal file
View File

@@ -0,0 +1,143 @@
// Copyright (c) 2022 Multus 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 api
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/logging"
)
// ShimNetConf for the SHIM cni config file written in json
type ShimNetConf struct {
// Note: This struct contains NetConf in pkg/types, but this struct is only used to parse
// following fields, so we skip to include NetConf here. Other fields are directly send to
// multus-daemon as a part of skel.CmdArgs, StdinData.
// types.NetConf
CNIVersion string `json:"cniVersion,omitempty"`
MultusSocketDir string `json:"daemonSocketDir"`
LogFile string `json:"logFile,omitempty"`
LogLevel string `json:"logLevel,omitempty"`
LogToStderr bool `json:"logToStderr,omitempty"`
}
// readyCheckFunc defines a type for API readiness check functions
type readyCheckFunc func(string) error
// CmdAdd implements the CNI spec ADD command handler
func CmdAdd(args *skel.CmdArgs) error {
response, cniVersion, err := postRequest(args, WaitUntilAPIReady)
if err != nil {
return logging.Errorf("CmdAdd (shim): %v", err)
}
logging.Verbosef("CmdAdd (shim): %v", *response.Result)
return cnitypes.PrintResult(response.Result, cniVersion)
}
// CmdCheck implements the CNI spec CHECK command handler
func CmdCheck(args *skel.CmdArgs) error {
_, _, err := postRequest(args, WaitUntilAPIReady)
if err != nil {
return logging.Errorf("CmdCheck (shim): %v", err)
}
return err
}
// CmdDel implements the CNI spec DEL command handler
func CmdDel(args *skel.CmdArgs) error {
_, _, err := postRequest(args, CheckAPIReadyNow)
if err != nil {
// No error in DEL (as of CNI spec)
logging.Errorf("CmdDel (shim): %v", err)
}
return nil
}
func postRequest(args *skel.CmdArgs, readinessCheck readyCheckFunc) (*Response, string, error) {
multusShimConfig, err := shimConfig(args.StdinData)
if err != nil {
return nil, "", fmt.Errorf("invalid CNI configuration passed to multus-shim: %w", err)
}
// Execute the readiness check as necessary (e.g. don't wait on CNI DEL)
if err := readinessCheck(multusShimConfig.MultusSocketDir); err != nil {
return nil, multusShimConfig.CNIVersion, err
}
cniRequest, err := newCNIRequest(args)
if err != nil {
return nil, multusShimConfig.CNIVersion, err
}
var body []byte
body, err = DoCNI("http://dummy/cni", cniRequest, SocketPath(multusShimConfig.MultusSocketDir))
if err != nil {
return nil, multusShimConfig.CNIVersion, fmt.Errorf("%s: StdinData: %s", err.Error(), string(args.StdinData))
}
response := &Response{}
if len(body) != 0 {
if err = json.Unmarshal(body, response); err != nil {
err = fmt.Errorf("failed to unmarshal response '%s': %v", string(body), err)
return nil, multusShimConfig.CNIVersion, err
}
}
return response, multusShimConfig.CNIVersion, nil
}
// Create and fill a Request with this Plugin's environment and stdin which
// contain the CNI variables and configuration
func newCNIRequest(args *skel.CmdArgs) (*Request, error) {
envMap := make(map[string]string)
for _, item := range os.Environ() {
idx := strings.Index(item, "=")
if idx > 0 {
envMap[strings.TrimSpace(item[:idx])] = item[idx+1:]
}
}
return &Request{
Env: envMap,
Config: args.StdinData,
}, nil
}
func shimConfig(cniConfig []byte) (*ShimNetConf, error) {
multusConfig := &ShimNetConf{}
if err := json.Unmarshal(cniConfig, multusConfig); err != nil {
return nil, fmt.Errorf("failed to gather the multus configuration: %w", err)
}
if multusConfig.MultusSocketDir == "" {
multusConfig.MultusSocketDir = defaultMultusRunDir
}
// Logging
logging.SetLogStderr(multusConfig.LogToStderr)
if multusConfig.LogFile != "" {
logging.SetLogFile(multusConfig.LogFile)
}
if multusConfig.LogLevel != "" {
logging.SetLogLevel(multusConfig.LogLevel)
}
return multusConfig, nil
}

28
pkg/server/api/socket.go Normal file
View File

@@ -0,0 +1,28 @@
// Copyright (c) 2022 Multus 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 api
import (
"path/filepath"
)
const (
serverSocketName = "multus.sock"
)
// SocketPath returns the path of the multus CNI socket
func SocketPath(rundir string) string {
return filepath.Join(rundir, serverSocketName)
}

Some files were not shown because too many files have changed in this diff Show More