├── .dockerignore ├── hack ├── e2e │ ├── .gitignore │ ├── util.sh │ ├── helm.sh │ ├── README.md │ ├── ecr.sh │ ├── eksctl.sh │ └── kops.sh ├── values.yaml ├── kops-patch-node.yaml ├── kops-patch.yaml ├── update-gofmt ├── verify-govet ├── verify-all ├── verify-gofmt ├── verify-golint ├── update-gomod ├── provenance ├── update-gomock └── release ├── .github ├── cr.yaml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── support-request.md │ ├── feature_request.md │ └── enhancement-request.md └── workflows │ ├── helm-chart-release.yaml │ ├── release.yaml │ └── container-image.yaml ├── .gitignore ├── OWNERS ├── deploy └── kubernetes │ ├── overlays │ └── stable │ │ ├── kustomization.yaml │ │ ├── ecr-public │ │ └── kustomization.yaml │ │ └── ecr │ │ └── kustomization.yaml │ ├── secret.yaml │ └── base │ ├── node-serviceaccount.yaml │ ├── csidriver.yaml │ ├── kustomization.yaml │ ├── clusterrolebinding-csi-node.yaml │ ├── clusterrole-csi-node.yaml │ ├── controller-serviceaccount.yaml │ ├── node-daemonset.yaml │ └── controller-deployment.yaml ├── code-of-conduct.md ├── examples └── kubernetes │ ├── efa │ ├── specs │ │ ├── claim.yaml │ │ ├── storageclass.yaml │ │ └── pod.yaml │ └── README.md │ ├── max_cache_tuning │ ├── specs │ │ ├── claim.yaml │ │ ├── storageclass.yaml │ │ └── pod.yaml │ └── README.md │ ├── multiple_pods │ ├── specs │ │ ├── claim.yaml │ │ ├── storageclass.yaml │ │ ├── pod1.yaml │ │ └── pod2.yaml │ └── README.md │ ├── dynamic_provisioning │ ├── specs │ │ ├── claim.yaml │ │ ├── pod.yaml │ │ └── storageclass.yaml │ └── README.md │ ├── dynamic_provisioning_s3 │ ├── specs │ │ ├── claim.yaml │ │ ├── storageclass.yaml │ │ └── pod.yaml │ └── README.md │ └── static_provisioning │ ├── specs │ ├── claim.yaml │ ├── pod.yaml │ └── pv.yaml │ └── README.md ├── charts └── aws-fsx-csi-driver │ ├── templates │ ├── csidriver.yaml │ ├── clusterrolebinding-csi-node.yaml │ ├── clusterrole-csi-node.yaml │ ├── node-serviceaccount.yaml │ ├── pdb.yaml │ ├── _helpers.tpl │ ├── controller-serviceaccount.yaml │ └── node-daemonset.yaml │ ├── .helmignore │ ├── Chart.yaml │ └── CHANGELOG.md ├── pkg ├── driver │ ├── constants.go │ ├── fakes.go │ ├── version.go │ ├── mount.go │ ├── version_test.go │ ├── identity.go │ ├── internal │ │ ├── inflight.go │ │ └── inflight_test.go │ ├── driver.go │ └── mocks │ │ └── mock_cloud.go ├── cloud │ ├── metadata_ec2.go │ ├── metadata_k8s.go │ ├── metadata.go │ ├── mocks │ │ ├── mock_ec2metadata.go │ │ ├── mock_metadata.go │ │ └── mock_fsx.go │ └── fakes.go └── util │ └── util.go ├── SECURITY_CONTACTS ├── CONTRIBUTING.md ├── cmd ├── options │ ├── controller_options_test.go │ ├── node_options.go │ ├── controller_options.go │ ├── server_options.go │ ├── node_options_test.go │ └── server_options_test.go ├── main.go └── options.go ├── docs └── options.md ├── tests ├── e2e │ ├── driver │ │ ├── fsx_csi_driver.go │ │ └── driver.go │ ├── testsuites │ │ ├── dynamically_provisioned_cmd_volume_tester.go │ │ └── specs.go │ ├── suite_test.go │ ├── cloud.go │ ├── conformance_test.go │ └── dynamic_provisioning_test.go └── sanity │ └── sanity_test.go ├── Dockerfile ├── tester └── e2e-test-config.yaml ├── go.mod └── Makefile /.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /hack/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | /csi-test-artifacts 2 | -------------------------------------------------------------------------------- /hack/values.yaml: -------------------------------------------------------------------------------- 1 | controller: 2 | replicaCount: 1 3 | -------------------------------------------------------------------------------- /.github/cr.yaml: -------------------------------------------------------------------------------- 1 | release-name-template: "helm-chart-{{ .Name }}-{{ .Version }}" 2 | -------------------------------------------------------------------------------- /hack/kops-patch-node.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | instanceMetadata: 3 | httpPutResponseHopLimit: 3 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # intellij idea 3 | .idea 4 | 5 | .DS_Store 6 | *.swp 7 | bin/ 8 | *~ 9 | 10 | # Vendor dir 11 | vendor/ 12 | 13 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - olemarkus 5 | - nckturner 6 | - khoang98 7 | - torredil 8 | -------------------------------------------------------------------------------- /deploy/kubernetes/overlays/stable/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ./ecr-public -------------------------------------------------------------------------------- /deploy/kubernetes/overlays/stable/ecr-public/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | bases: 4 | - ../../../base 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Is this a bug fix or adding new feature?** 2 | 3 | **What is this PR about? / Why do we need it?** 4 | 5 | **What testing is done?** 6 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /deploy/kubernetes/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: aws-secret 5 | namespace: kube-system 6 | stringData: 7 | key_id: "[aws_access_key_id]" 8 | access_key: "[aws_secret_access_key]" 9 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/node-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/node-serviceaccount.yaml 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: fsx-csi-node-sa 7 | labels: 8 | app.kubernetes.io/name: aws-fsx-csi-driver 9 | -------------------------------------------------------------------------------- /examples/kubernetes/efa/specs/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: fsx-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | storageClassName: fsx-sc 9 | resources: 10 | requests: 11 | storage: 4800Gi 12 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/csidriver.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/csidriver.yaml 3 | apiVersion: storage.k8s.io/v1 4 | kind: CSIDriver 5 | metadata: 6 | name: fsx.csi.aws.com 7 | spec: 8 | attachRequired: false 9 | fsGroupPolicy: ReadWriteOnceWithFSType 10 | -------------------------------------------------------------------------------- /examples/kubernetes/max_cache_tuning/specs/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: fsx-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | storageClassName: fsx-sc 9 | resources: 10 | requests: 11 | storage: 1200Gi 12 | -------------------------------------------------------------------------------- /examples/kubernetes/multiple_pods/specs/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: fsx-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | storageClassName: fsx-sc 9 | resources: 10 | requests: 11 | storage: 1200Gi 12 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning/specs/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: fsx-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | storageClassName: fsx-sc 9 | resources: 10 | requests: 11 | storage: 1200Gi 12 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning_s3/specs/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: fsx-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | storageClassName: fsx-sc 9 | resources: 10 | requests: 11 | storage: 1200Gi 12 | -------------------------------------------------------------------------------- /examples/kubernetes/static_provisioning/specs/claim.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: fsx-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | storageClassName: "" 9 | resources: 10 | requests: 11 | storage: 1200Gi 12 | volumeName: fsx-pv 13 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/csidriver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: {{ ternary "storage.k8s.io/v1" "storage.k8s.io/v1beta1" (semverCompare ">=1.18.0-0" .Capabilities.KubeVersion.Version) }} 2 | kind: CSIDriver 3 | metadata: 4 | name: fsx.csi.aws.com 5 | spec: 6 | attachRequired: false 7 | fsGroupPolicy: {{ .Values.csidriver.fsGroupPolicy }} 8 | -------------------------------------------------------------------------------- /deploy/kubernetes/overlays/stable/ecr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | bases: 4 | - ../../../base 5 | images: 6 | - name: public.ecr.aws/fsx-csi-driver/aws-fsx-csi-driver 7 | newName: 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/aws-fsx-csi-driver 8 | newTag: v1.1.0 9 | -------------------------------------------------------------------------------- /examples/kubernetes/multiple_pods/specs/storageclass.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | name: fsx-sc 5 | provisioner: fsx.csi.aws.com 6 | parameters: 7 | subnetId: subnet-056da83524edbe641 8 | securityGroupIds: sg-086f61ea73388fb6b 9 | deploymentType: SCRATCH_2 10 | mountOptions: 11 | - flock 12 | -------------------------------------------------------------------------------- /examples/kubernetes/max_cache_tuning/specs/storageclass.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | name: fsx-sc 5 | provisioner: fsx.csi.aws.com 6 | parameters: 7 | subnetId: subnet-0d7b5e117ad7b4961 8 | securityGroupIds: sg-05a37bfe01467059a 9 | deploymentType: SCRATCH_2 10 | mountOptions: 11 | - flock 12 | -------------------------------------------------------------------------------- /pkg/driver/constants.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | // constants for default command line flag values 4 | const ( 5 | DefaultCSIEndpoint = "unix://tmp/csi.sock" 6 | ) 7 | 8 | // constants for node k8s API use 9 | const ( 10 | // AgentNotReadyNodeTaintKey contains the key of taints to be removed on driver startup 11 | AgentNotReadyNodeTaintKey = "fsx.csi.aws.com/agent-not-ready" 12 | ) 13 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: kube-system 4 | resources: 5 | - csidriver.yaml 6 | - node-daemonset.yaml 7 | - node-serviceaccount.yaml 8 | - controller-deployment.yaml 9 | - controller-serviceaccount.yaml 10 | - clusterrole-csi-node.yaml 11 | - clusterrolebinding-csi-node.yaml 12 | 13 | -------------------------------------------------------------------------------- /hack/e2e/util.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uo pipefail 4 | 5 | function loudecho() { 6 | echo "###" 7 | echo "## ${1}" 8 | echo "#" 9 | } 10 | 11 | function generate_ssh_key() { 12 | SSH_KEY_PATH=${1} 13 | if [[ ! -e ${SSH_KEY_PATH} ]]; then 14 | loudecho "Generating SSH key $SSH_KEY_PATH" 15 | ssh-keygen -P csi-e2e -f "${SSH_KEY_PATH}" 16 | else 17 | loudecho "Reusing SSH key $SSH_KEY_PATH" 18 | fi 19 | } 20 | -------------------------------------------------------------------------------- /examples/kubernetes/efa/specs/storageclass.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | name: fsx-sc 5 | provisioner: fsx.csi.aws.com 6 | parameters: 7 | subnetId: 8 | securityGroupIds: 9 | deploymentType: PERSISTENT_2 10 | perUnitStorageThroughput: "1000" 11 | fileSystemTypeVersion: "2.15" 12 | metadataConfigurationMode: "AUTOMATIC" 13 | efaEnabled: "true" 14 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning_s3/specs/storageclass.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | name: fsx-sc 5 | provisioner: fsx.csi.aws.com 6 | parameters: 7 | subnetId: subnet-0d7b5e117ad7b4961 8 | securityGroupIds: sg-05a37bfe01467059a 9 | s3ImportPath: s3://ml-training-data-000 10 | s3ExportPath: s3://ml-training-data-000/export 11 | deploymentType: SCRATCH_2 12 | mountOptions: 13 | - flock 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the FSx for Lustre CSI Driver 4 | labels: 5 | 6 | --- 7 | 8 | /kind bug 9 | 10 | **What happened?** 11 | 12 | **What you expected to happen?** 13 | 14 | **How to reproduce it (as minimally and precisely as possible)?** 15 | 16 | **Anything else we need to know?**: 17 | 18 | **Environment** 19 | - Kubernetes version (use `kubectl version`): 20 | - Driver version: 21 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /examples/kubernetes/multiple_pods/specs/pod1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: app1 5 | spec: 6 | containers: 7 | - name: app1 8 | image: amazonlinux:2 9 | command: ["/bin/sh"] 10 | args: ["-c", "while true; do echo $(date -u) >> /data/out1.txt; sleep 5; done"] 11 | volumeMounts: 12 | - name: persistent-storage 13 | mountPath: /data 14 | volumes: 15 | - name: persistent-storage 16 | persistentVolumeClaim: 17 | claimName: fsx-claim 18 | -------------------------------------------------------------------------------- /examples/kubernetes/multiple_pods/specs/pod2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: app2 5 | spec: 6 | containers: 7 | - name: app2 8 | image: amazonlinux:2 9 | command: ["/bin/sh"] 10 | args: ["-c", "while true; do echo $(date -u) >> /data/out2.txt; sleep 5; done"] 11 | volumeMounts: 12 | - name: persistent-storage 13 | mountPath: /data 14 | volumes: 15 | - name: persistent-storage 16 | persistentVolumeClaim: 17 | claimName: fsx-claim 18 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning/specs/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: fsx-app 5 | spec: 6 | containers: 7 | - name: app 8 | image: amazonlinux:2 9 | command: ["/bin/sh"] 10 | args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"] 11 | volumeMounts: 12 | - name: persistent-storage 13 | mountPath: /data 14 | volumes: 15 | - name: persistent-storage 16 | persistentVolumeClaim: 17 | claimName: fsx-claim 18 | -------------------------------------------------------------------------------- /examples/kubernetes/static_provisioning/specs/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: fsx-app 5 | spec: 6 | containers: 7 | - name: app 8 | image: amazonlinux:2 9 | command: ["/bin/sh"] 10 | args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"] 11 | volumeMounts: 12 | - name: persistent-storage 13 | mountPath: /data 14 | volumes: 15 | - name: persistent-storage 16 | persistentVolumeClaim: 17 | claimName: fsx-claim 18 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/clusterrolebinding-csi-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/clusterrolebinding-csi-node.yaml 3 | kind: ClusterRoleBinding 4 | apiVersion: rbac.authorization.k8s.io/v1 5 | metadata: 6 | name: fsx-csi-node-getter-binding 7 | labels: 8 | app.kubernetes.io/name: aws-fsx-csi-driver 9 | subjects: 10 | - kind: ServiceAccount 11 | name: fsx-csi-node-sa 12 | roleRef: 13 | kind: ClusterRole 14 | name: fsx-csi-node-role 15 | apiGroup: rbac.authorization.k8s.io 16 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: "1.8.0" 3 | name: aws-fsx-csi-driver 4 | description: A Helm chart for AWS FSx for Lustre CSI Driver 5 | version: 1.15.0 6 | kubeVersion: ">=1.17.0-0" 7 | home: https://github.com/kubernetes-sigs/aws-fsx-csi-driver 8 | sources: 9 | - https://github.com/kubernetes-sigs/aws-fsx-csi-driver 10 | keywords: 11 | - aws 12 | - fsx 13 | - csi 14 | maintainers: 15 | - name: Kubernetes Authors 16 | url: https://github.com/kubernetes-sigs/aws-fsx-csi-driver/ 17 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/clusterrolebinding-csi-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ClusterRoleBinding 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: fsx-csi-node-getter-binding 6 | labels: 7 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 8 | subjects: 9 | - kind: ServiceAccount 10 | name: {{ .Values.node.serviceAccount.name }} 11 | namespace: {{ .Release.Namespace }} 12 | roleRef: 13 | kind: ClusterRole 14 | name: fsx-csi-node-role 15 | apiGroup: rbac.authorization.k8s.io 16 | 17 | -------------------------------------------------------------------------------- /examples/kubernetes/static_provisioning/specs/pv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: fsx-pv 5 | spec: 6 | capacity: 7 | storage: 1200Gi 8 | volumeMode: Filesystem 9 | accessModes: 10 | - ReadWriteMany 11 | mountOptions: 12 | - flock 13 | persistentVolumeReclaimPolicy: Retain 14 | csi: 15 | driver: fsx.csi.aws.com 16 | volumeHandle: fs-0199e5a63bd90f796 17 | volumeAttributes: 18 | dnsname: fs-0199e5a63bd90f796.fsx.us-east-1.amazonaws.com 19 | mountname: fsx 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: Ask questions about the driver 4 | labels: 5 | 6 | --- 7 | 8 | 17 | 18 | 19 | 20 | /triage support 21 | -------------------------------------------------------------------------------- /hack/e2e/helm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uo pipefail 4 | 5 | function helm_install() { 6 | INSTALL_PATH=${1} 7 | 8 | if [[ ! -e ${INSTALL_PATH}/helm ]]; then 9 | mkdir -p ${INSTALL_PATH} 10 | pushd ${INSTALL_PATH} > /dev/null || return 1 11 | 12 | curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 13 | chmod 700 get_helm.sh 14 | export USE_SUDO=false 15 | export HELM_INSTALL_DIR=${INSTALL_PATH} 16 | ./get_helm.sh 17 | rm get_helm.sh 18 | 19 | popd > /dev/null 20 | fi 21 | } 22 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | leakingtapan 14 | 15 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/clusterrole-csi-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ClusterRole 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: fsx-csi-node-role 6 | labels: 7 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: [""] 10 | resources: ["nodes"] 11 | verbs: ["get", "patch"] 12 | - apiGroups: [ "" ] 13 | resources: [ "pods" ] 14 | verbs: [ "get", "list", "watch" ] 15 | - apiGroups: [ "" ] 16 | resources: [ "persistentvolumes" ] 17 | verbs: [ "get" ] 18 | - apiGroups: [ "" ] 19 | resources: [ "persistentvolumeclaims" ] 20 | verbs: [ "get"] 21 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | name: fsx-sc 5 | provisioner: fsx.csi.aws.com 6 | parameters: 7 | subnetId: subnet-0eabfaa81fb22bcaf 8 | securityGroupIds: sg-068000ccf82dfba88 9 | deploymentType: PERSISTENT_1 10 | automaticBackupRetentionDays: "1" 11 | dailyAutomaticBackupStartTime: "00:00" 12 | copyTagsToBackups: "true" 13 | perUnitStorageThroughput: "200" 14 | dataCompressionType: "NONE" 15 | weeklyMaintenanceStartTime: "7:09:00" 16 | fileSystemTypeVersion: "2.12" 17 | extraTags: "Tag1=Value1,Tag2=Value2" 18 | mountOptions: 19 | - flock 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | ## Sign the CLA 4 | 5 | Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests. Please see https://git.k8s.io/community/CLA.md for more info 6 | 7 | ### Contributing A Patch 8 | 9 | 1. Submit an issue describing your proposed change to the repo in question. 10 | 1. The [repo owners](OWNERS) will respond to your issue promptly. 11 | 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 12 | 1. Fork the desired repo, develop and test your code changes. 13 | 1. Submit a pull request. 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: 5 | 6 | --- 7 | 8 | **Is your feature request related to a problem? Please describe.** 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | **Describe the solution you'd like in detail** 12 | A clear and concise description of what you want to happen. 13 | 14 | **Describe alternatives you've considered** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/clusterrole-csi-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/clusterrole-csi-node.yaml 3 | kind: ClusterRole 4 | apiVersion: rbac.authorization.k8s.io/v1 5 | metadata: 6 | name: fsx-csi-node-role 7 | labels: 8 | app.kubernetes.io/name: aws-fsx-csi-driver 9 | rules: 10 | - apiGroups: [""] 11 | resources: ["nodes"] 12 | verbs: ["get", "patch"] 13 | - apiGroups: [ "" ] 14 | resources: [ "pods" ] 15 | verbs: [ "get", "list", "watch" ] 16 | - apiGroups: [ "" ] 17 | resources: [ "persistentvolumes" ] 18 | verbs: [ "get" ] 19 | - apiGroups: [ "" ] 20 | resources: [ "persistentvolumeclaims" ] 21 | verbs: [ "get"] 22 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning_s3/specs/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: fsx-app 5 | spec: 6 | containers: 7 | - name: app 8 | image: amazonlinux:2 9 | command: ["/bin/sh"] 10 | securityContext: 11 | privileged: true 12 | args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"] 13 | lifecycle: 14 | postStart: 15 | exec: 16 | command: ["amazon-linux-extras", "install", "lustre", "-y"] 17 | volumeMounts: 18 | - name: persistent-storage 19 | mountPath: /data 20 | volumes: 21 | - name: persistent-storage 22 | persistentVolumeClaim: 23 | claimName: fsx-claim 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Suggest an idea for this project 4 | labels: 5 | 6 | --- 7 | 8 | **Is your feature request related to a problem?/Why is this needed** 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | /feature 12 | 13 | **Describe the solution you'd like in detail** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /hack/e2e/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | run.sh will build and push a driver image, create a kops cluster, helm install the driver pointing to the built image, run ginkgo tests, then clean everything up. 4 | 5 | See below for an example. 6 | 7 | KOPS_STATE_FILE is an S3 bucket you have write access to. 8 | 9 | TEST_ID is a token used for idempotency. 10 | 11 | For more details, see the script itself. 12 | 13 | For more examples, see the top-level Makefile. 14 | 15 | ``` 16 | TEST_PATH=./tests/e2e/... \ 17 | TEST_ID=18512 \ 18 | CLEAN=false \ 19 | KOPS_STATE_FILE=s3://mattwon \ 20 | AWS_REGION=us-west-2 \ 21 | AWS_AVAILABILITY_ZONES=us-west-2a \ 22 | GINKGO_FOCUS=".*" \ 23 | GINKGO_NODES=1 \ 24 | ./hack/e2e/run.sh 25 | ``` 26 | -------------------------------------------------------------------------------- /examples/kubernetes/max_cache_tuning/specs/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: fsx-app 5 | spec: 6 | initContainers: 7 | - name: set-lustre-cache 8 | image: amazonlinux:2 9 | command: ["/bin/sh","-c"] 10 | args: ["amazon-linux-extras install lustre -y && /sbin/lctl set_param llite.*.max_cached_mb=32"] 11 | securityContext: 12 | privileged: true 13 | containers: 14 | - name: app 15 | image: amazonlinux:2 16 | command: ["/bin/sh"] 17 | args: ["-c", "sleep 999999"] 18 | volumeMounts: 19 | - name: persistent-storage 20 | mountPath: /data 21 | volumes: 22 | - name: persistent-storage 23 | persistentVolumeClaim: 24 | claimName: fsx-claim 25 | -------------------------------------------------------------------------------- /hack/kops-patch.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | additionalPolicies: 3 | node: | 4 | [ 5 | { 6 | "Effect": "Allow", 7 | "Action": [ 8 | "iam:CreateServiceLinkedRole", 9 | "iam:AttachRolePolicy", 10 | "iam:PutRolePolicy" 11 | ], 12 | "Resource": "arn:aws:iam::*:role/aws-service-role/s3.data-source.lustre.fsx.amazonaws.com/*" 13 | }, 14 | { 15 | "Effect": "Allow", 16 | "Action": [ 17 | "s3:ListBucket", 18 | "fsx:CreateFileSystem", 19 | "fsx:DeleteFileSystem", 20 | "fsx:DescribeFileSystems", 21 | "fsx:TagResource" 22 | ], 23 | "Resource": ["*"] 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/node-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.node.serviceAccount.create }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ .Values.node.serviceAccount.name }} 6 | labels: 7 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 8 | {{- with .Values.node.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- else }} 13 | {{- $exists := (lookup "v1" "ServiceAccount" .Release.Namespace .Values.node.serviceAccount.name) }} 14 | {{- if not $exists }} 15 | {{- fail (printf "create serviceaccount %s/%s or set .node.serviceaccount.create true" .Release.Namespace .Values.node.serviceAccount.name) }} 16 | {{- end }} 17 | {{- end }} 18 | --- 19 | -------------------------------------------------------------------------------- /hack/update-gofmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | find . -name "*.go" | grep -v "\/vendor\/" | xargs gofmt -s -w 20 | -------------------------------------------------------------------------------- /cmd/options/controller_options_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestControllerOptions(t *testing.T) { 24 | } 25 | -------------------------------------------------------------------------------- /hack/verify-govet: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | echo "Verifying govet" 20 | 21 | go vet $(go list ./... | grep -v vendor) 22 | 23 | echo "Done" 24 | -------------------------------------------------------------------------------- /examples/kubernetes/efa/README.md: -------------------------------------------------------------------------------- 1 | # EFA Example 2 | 3 | This example demonstrates how to provision and configure an FSx Lustre file system with EFA (Elastic Fabric Adapter) enabled for high-performance networking, and tune Lustre client parameters after mount. 4 | 5 | ## Overview 6 | 7 | The example provisions an FSx Lustre file system with EFA networking enabled (`efaEnabled: "true"`) for enhanced performance, then uses an init container to optimize Lustre client settings. 8 | 9 | ## Key Components 10 | 11 | - **StorageClass**: Configures FSx Lustre with EFA enabled and high throughput settings 12 | - **PersistentVolumeClaim**: Requests 4800Gi of storage using the EFA-enabled storage class 13 | - **Init Container**: Tunes Lustre client parameters for optimal performance 14 | - **Application Pod**: Mounts the high-performance FSx volume at `/data` 15 | 16 | -------------------------------------------------------------------------------- /hack/verify-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | PKG_ROOT=$(git rev-parse --show-toplevel) 20 | 21 | ${PKG_ROOT}/hack/verify-gofmt 22 | ${PKG_ROOT}/hack/verify-govet 23 | ${PKG_ROOT}/hack/verify-golint 24 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/pdb.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.controller.podDisruptionBudget.enabled (not .Values.nodeComponentOnly) -}} 2 | apiVersion: policy/v1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: fsx-csi-controller 6 | namespace: kube-system 7 | labels: 8 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: fsx-csi-controller 13 | {{- include "aws-fsx-csi-driver.selectorLabels" . | nindent 6 }} 14 | {{- if .Values.controller.podDisruptionBudget.unhealthyPodEvictionPolicy }} 15 | unhealthyPodEvictionPolicy: {{ .Values.controller.podDisruptionBudget.unhealthyPodEvictionPolicy }} 16 | {{- end }} 17 | {{- if le (.Values.controller.replicaCount | int) 2 }} 18 | maxUnavailable: 1 19 | {{- else }} 20 | minAvailable: 2 21 | {{- end }} 22 | {{- end -}} -------------------------------------------------------------------------------- /.github/workflows/helm-chart-release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Helm Charts 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - "charts/**" 9 | 10 | jobs: 11 | release: 12 | permissions: 13 | contents: write # Needed for creating and editing releases 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Configure Git 21 | run: | 22 | git config user.name "$GITHUB_ACTOR" 23 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 24 | - name: Run chart-releaser 25 | uses: helm/chart-releaser-action@v1.6.0 26 | env: 27 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 28 | with: 29 | config: .github/cr.yaml 30 | mark_as_latest: false 31 | -------------------------------------------------------------------------------- /cmd/options/node_options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | flag "github.com/spf13/pflag" 21 | ) 22 | 23 | // NodeOptions contains options and configuration settings for the node service. 24 | type NodeOptions struct { 25 | } 26 | 27 | func (o *NodeOptions) AddFlags(fs *flag.FlagSet) { 28 | } 29 | -------------------------------------------------------------------------------- /hack/verify-gofmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | echo "Verifying gofmt" 20 | 21 | diff=$(find . -name "*.go" | grep -v "\/vendor\/" | xargs gofmt -s -d 2>&1) 22 | if [[ -n "${diff}" ]]; then 23 | echo "${diff}" 24 | echo 25 | echo "Please run hack/update-gofmt to fix the issue(s)" 26 | exit 1 27 | fi 28 | echo "No issue found" 29 | -------------------------------------------------------------------------------- /hack/verify-golint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | echo "Installing golangci-lint..." 20 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.21.0 21 | 22 | echo "Running golangci-lint..." 23 | ./bin/golangci-lint run --deadline=10m 24 | 25 | echo "Congratulations! All Go source files have been linted." 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | # Sequence of patterns matched against refs/tags 5 | tags: 6 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 7 | jobs: 8 | build: 9 | permissions: 10 | contents: write # Needed for creating and editing releases 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v1 16 | - name: Create Release 17 | id: create-release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: ${{ github.ref }} 24 | body: | 25 | AWS FSx CSI Driver 26 | ## CHANGELOG 27 | See [CHANGELOG](https://github.com/kubernetes-sigs/aws-fsx-csi-driver/blob/master/CHANGELOG-0.x.md) for full list of changes 28 | draft: false 29 | prerelease: false 30 | -------------------------------------------------------------------------------- /hack/e2e/ecr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -uo pipefail 4 | 5 | BASE_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") 6 | source "${BASE_DIR}"/util.sh 7 | 8 | function ecr_build_and_push() { 9 | REGION=${1} 10 | AWS_ACCOUNT_ID=${2} 11 | IMAGE_NAME=${3} 12 | IMAGE_TAG=${4} 13 | set +e 14 | if docker images --format "{{.Repository}}:{{.Tag}}" | grep "${IMAGE_NAME}:${IMAGE_TAG}"; then 15 | set -e 16 | loudecho "Assuming ${IMAGE_NAME}:${IMAGE_TAG} has been built and pushed" 17 | else 18 | set -e 19 | loudecho "Building and pushing test driver image to ${IMAGE_NAME}:${IMAGE_TAG}" 20 | aws ecr get-login-password --region "${REGION}" | docker login --username AWS --password-stdin "${AWS_ACCOUNT_ID}".dkr.ecr."${REGION}".amazonaws.com 21 | IMAGE=${IMAGE_NAME} TAG=${IMAGE_TAG} OS=linux ARCH=amd64 OSVERSION=amazon AL_VERSION="al23" make image 22 | docker tag "${IMAGE_NAME}":"${IMAGE_TAG}"-linux-amd64-amazon "${IMAGE_NAME}":"${IMAGE_TAG}" 23 | docker push "${IMAGE_NAME}":"${IMAGE_TAG}" 24 | fi 25 | } 26 | -------------------------------------------------------------------------------- /cmd/options/controller_options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | flag "github.com/spf13/pflag" 21 | ) 22 | 23 | // ControllerOptions contains options and configuration settings for the controller service. 24 | type ControllerOptions struct { 25 | // ExtraTags is a map of tags that will be attached to each dynamically provisioned resource. 26 | ExtraTags string 27 | } 28 | 29 | func (s *ControllerOptions) AddFlags(fs *flag.FlagSet) { 30 | fs.StringVar(&s.ExtraTags, "extra-tags", "", "Extra tags to attach to each dynamically provisioned resource. It is a comma separated list of key value pairs like '=,='") 31 | } 32 | -------------------------------------------------------------------------------- /docs/options.md: -------------------------------------------------------------------------------- 1 | # Driver Options 2 | There are a couple of driver options that can be passed as arguments when starting the driver container. 3 | 4 | | Option argument | value sample | default | Description | 5 | |-----------------------------|---------------------------------------------------|-----------------------------------------------------|---------------------------------------------------------------------------------------------| 6 | | endpoint | tcp://127.0.0.1:10000/ | unix:///var/lib/csi/sockets/pluginproxy/csi.sock | The socket on which the driver will listen for CSI RPCs | 7 | | extra-tags | key1=value1,key2=value2 | | Tags specified in the controller spec are attached to each dynamically provisioned resource | 8 | | logging-format | json | text | Sets the log format. Permitted formats: text, json | 9 | -------------------------------------------------------------------------------- /hack/update-gomod: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | set -euo pipefail 19 | set -x 20 | 21 | VERSION=${1#"v"} 22 | if [ -z "$VERSION" ]; then 23 | echo "Must specify version!" 24 | exit 1 25 | fi 26 | MODS=($( 27 | curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod | 28 | sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p' 29 | )) 30 | echo $MODS 31 | for MOD in "${MODS[@]}"; do 32 | echo $MOD 33 | V=$( 34 | go mod download -json "${MOD}@kubernetes-${VERSION}" | 35 | sed -n 's|.*"Version": "\(.*\)".*|\1|p' 36 | ) 37 | go mod edit "-replace=${MOD}=${MOD}@${V}" 38 | done 39 | -------------------------------------------------------------------------------- /examples/kubernetes/efa/specs/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: fsx-app 5 | spec: 6 | initContainers: 7 | - name: set-lustre-tunings 8 | image: amazonlinux:2023 9 | command: 10 | - /bin/sh 11 | - -c 12 | - | 13 | dnf install -y lustre-client | tail 14 | /sbin/lctl set_param ldlm.namespaces.*.lru_max_age=600000 15 | lru_size=$((100 * $(nproc))) 16 | if [ "$lru_size" -lt 1600 ]; then 17 | lru_size=1600 18 | fi 19 | /sbin/lctl set_param ldlm.namespaces.*.lru_size=$lru_size 20 | /sbin/lctl set_param llite.*.max_cached_mb=64 21 | /sbin/lctl set_param osc.*OST*.max_rpcs_in_flight=32 22 | /sbin/lctl set_param mdc.*.max_rpcs_in_flight=64 23 | /sbin/lctl set_param mdc.*.max_mod_rpcs_in_flight=50 24 | volumeMounts: 25 | - name: sys-fs-lustre 26 | mountPath: /sys/fs/lustre 27 | securityContext: 28 | privileged: true 29 | containers: 30 | - name: app 31 | image: amazonlinux:2 32 | command: ["/bin/sh"] 33 | args: ["-c", "sleep 999999"] 34 | volumeMounts: 35 | - name: persistent-storage 36 | mountPath: /data 37 | volumes: 38 | - name: persistent-storage 39 | persistentVolumeClaim: 40 | claimName: fsx-claim 41 | - name: sys-fs-lustre 42 | hostPath: 43 | path: /sys/fs/lustre 44 | type: Directory 45 | -------------------------------------------------------------------------------- /cmd/options/server_options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | flag "github.com/spf13/pflag" 21 | 22 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver" 23 | ) 24 | 25 | // ServerOptions contains options and configuration settings for the driver server. 26 | type ServerOptions struct { 27 | // DriverMode is the service mode the driver server should run in. 28 | DriverMode string 29 | // Endpoint is the endpoint that the driver server should listen on. 30 | Endpoint string 31 | } 32 | 33 | func (s *ServerOptions) AddFlags(fs *flag.FlagSet) string { 34 | fs.StringVar(&s.DriverMode, "mode", driver.AllMode, "Service mode the driver server should run in") 35 | fs.StringVar(&s.Endpoint, "endpoint", driver.DefaultCSIEndpoint, "Endpoint for the CSI driver server") 36 | 37 | return s.DriverMode 38 | } 39 | -------------------------------------------------------------------------------- /cmd/options/node_options_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "testing" 21 | 22 | flag "github.com/spf13/pflag" 23 | ) 24 | 25 | func TestNodeOptions(t *testing.T) { 26 | testCases := []struct { 27 | name string 28 | flag string 29 | found bool 30 | }{ 31 | { 32 | name: "fail for non-desired flag", 33 | flag: "some-flag", 34 | found: false, 35 | }, 36 | } 37 | 38 | for _, tc := range testCases { 39 | flagSet := flag.NewFlagSet("test-flagset", flag.ContinueOnError) 40 | nodeOptions := &NodeOptions{} 41 | 42 | t.Run(tc.name, func(t *testing.T) { 43 | nodeOptions.AddFlags(flagSet) 44 | 45 | flag := flagSet.Lookup(tc.flag) 46 | found := flag != nil 47 | if found != tc.found { 48 | t.Fatalf("result not equal\ngot:\n%v\nexpected:\n%v", found, tc.found) 49 | } 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/options/server_options_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "testing" 21 | 22 | flag "github.com/spf13/pflag" 23 | ) 24 | 25 | func TestServerOptions(t *testing.T) { 26 | testCases := []struct { 27 | name string 28 | flag string 29 | found bool 30 | }{ 31 | { 32 | name: "lookup desired flag", 33 | flag: "endpoint", 34 | found: true, 35 | }, 36 | { 37 | name: "fail for non-desired flag", 38 | flag: "some-other-flag", 39 | found: false, 40 | }, 41 | } 42 | 43 | for _, tc := range testCases { 44 | flagSet := flag.NewFlagSet("test-flagset", flag.ContinueOnError) 45 | serverOptions := &ServerOptions{} 46 | 47 | t.Run(tc.name, func(t *testing.T) { 48 | serverOptions.AddFlags(flagSet) 49 | 50 | flag := flagSet.Lookup(tc.flag) 51 | found := flag != nil 52 | if found != tc.found { 53 | t.Fatalf("result not equal\ngot:\n%v\nexpected:\n%v", found, tc.found) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/kubernetes/static_provisioning/README.md: -------------------------------------------------------------------------------- 1 | ## Static Provisioning Example 2 | This example shows how to make a pre-created FSx for Lustre filesystem mounted inside container. 3 | 4 | ### Edit [Persistent Volume Spec](./specs/pv.yaml) 5 | ``` 6 | apiVersion: v1 7 | kind: PersistentVolume 8 | metadata: 9 | name: fsx-pv 10 | spec: 11 | capacity: 12 | storage: 1200Gi 13 | volumeMode: Filesystem 14 | accessModes: 15 | - ReadWriteMany 16 | mountOptions: 17 | - flock 18 | persistentVolumeReclaimPolicy: Recycle 19 | csi: 20 | driver: fsx.csi.aws.com 21 | volumeHandle: [FileSystemId] 22 | volumeAttributes: 23 | dnsname: [DNSName] 24 | mountname: [MountName] 25 | ``` 26 | Replace `volumeHandle` with `FileSystemId`, `dnsname` with `DNSName` and `mountname` with `MountName`. You can get both `FileSystemId`, `DNSName` and `MountName` using AWS CLI: 27 | 28 | ```sh 29 | >> aws fsx describe-file-systems 30 | ``` 31 | 32 | ### Deploy the Application 33 | Create PV, persistent volume claim (PVC), and the pod that consumes the PV: 34 | ```sh 35 | >> kubectl apply -f examples/kubernetes/static_provisioning/specs/pv.yaml 36 | >> kubectl apply -f examples/kubernetes/static_provisioning/specs/claim.yaml 37 | >> kubectl apply -f examples/kubernetes/static_provisioning/specs/pod.yaml 38 | ``` 39 | 40 | ### Check the Application uses FSx for Lustre filesystem 41 | After the objects are created, verify that pod is running: 42 | 43 | ```sh 44 | >> kubectl get pods 45 | ``` 46 | 47 | Also verify that data is written onto FSx for Luster filesystem: 48 | 49 | ```sh 50 | >> kubectl exec -ti fsx-app -- tail -f /data/out.txt 51 | ``` 52 | -------------------------------------------------------------------------------- /pkg/driver/fakes.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package driver 18 | 19 | import ( 20 | "k8s.io/mount-utils" 21 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud" 22 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver/internal" 23 | ) 24 | 25 | func NewFakeMounter() Mounter { 26 | return &NodeMounter{ 27 | Interface: &mount.FakeMounter{ 28 | MountPoints: []mount.MountPoint{}, 29 | }, 30 | } 31 | } 32 | 33 | // NewFakeDriver creates a new mock driver used for testing 34 | func NewFakeDriver(endpoint string) *Driver { 35 | driverOptions := DriverOptions{ 36 | endpoint: endpoint, 37 | mode: AllMode, 38 | } 39 | 40 | driver := &Driver{ 41 | options: &driverOptions, 42 | controllerService: controllerService{ 43 | cloud: cloud.NewFakeCloudProvider(), 44 | inFlight: internal.NewInFlight(), 45 | driverOptions: &driverOptions, 46 | }, 47 | nodeService: nodeService{ 48 | mounter: NewFakeMounter(), 49 | inFlight: internal.NewInFlight(), 50 | driverOptions: &DriverOptions{}, 51 | }, 52 | } 53 | return driver 54 | } 55 | -------------------------------------------------------------------------------- /hack/provenance: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2023 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # There is no reliable way to check if a buildx installation supports 18 | # --provenance other than trying to execute it. You cannot even rely 19 | # on the version, because buildx's own installation docs will result 20 | # in installations of buildx that do not correctly report their version 21 | # via `docker buildx version`. 22 | # 23 | # Additionally, if the local buildkit worker is the Docker daemon, 24 | # attestation should not be supported and must be disabled. 25 | # 26 | # Thus, this script echos back the flag `--provenance=false` if and only 27 | # if the local buildx installation supports it. If not, it exits silently. 28 | 29 | BUILDX_TEST=$(docker buildx build --provenance=false 2>&1) 30 | if [[ "${BUILDX_TEST}" == *"'docker buildx build --help'"* ]]; then 31 | if [[ "${BUILDX_TEST}" == *"1 argument"* ]] && ! docker buildx inspect | grep -qE "^Driver:\s*docker$"; then 32 | echo "--provenance=false" 33 | fi 34 | else 35 | echo "Local buildx installation broken?" >&2 36 | exit 1 37 | fi 38 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | flag "github.com/spf13/pflag" 21 | logsapi "k8s.io/component-base/logs/api/v1" 22 | json "k8s.io/component-base/logs/json" 23 | 24 | "k8s.io/klog/v2" 25 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver" 26 | ) 27 | 28 | func main() { 29 | fs := flag.NewFlagSet("aws-fsx-csi-driver", flag.ExitOnError) 30 | 31 | if err := logsapi.RegisterLogFormat(logsapi.JSONLogFormat, json.Factory{}, logsapi.LoggingBetaOptions); err != nil { 32 | klog.ErrorS(err, "failed to register JSON log format") 33 | } 34 | 35 | options := GetOptions(fs) 36 | 37 | drv, err := driver.NewDriver( 38 | driver.WithEndpoint(options.ServerOptions.Endpoint), 39 | driver.WithMode(options.ServerOptions.DriverMode), 40 | driver.WithExtraTags(options.ControllerOptions.ExtraTags), 41 | ) 42 | 43 | if err != nil { 44 | klog.ErrorS(err, "failed to create driver") 45 | klog.FlushAndExit(klog.ExitFlushTimeout, 1) 46 | } 47 | if err := drv.Run(); err != nil { 48 | klog.ErrorS(err, "failed to run driver") 49 | klog.FlushAndExit(klog.ExitFlushTimeout, 1) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/kubernetes/max_cache_tuning/README.md: -------------------------------------------------------------------------------- 1 | ## Tuning Lustre Max Memory Cache 2 | This example shows how to set lustre `llite.*.max_cached_mb` using init container. Lustre client interacts with lustre kernel module for data caching at host level. Since the cache resides in kernel space, it won't be counted toward application container's memory limit. Sometimes it is desireable to reduce the lustre cache size to limit memory consumption at host level. In this example, the max cache size is set to 32MB, but other values may be selected depending on what makes sense for the workload. 3 | 4 | ### Edit [Pod](./specs/pod.yaml) 5 | ``` 6 | apiVersion: v1 7 | kind: Pod 8 | metadata: 9 | name: fsx-app 10 | spec: 11 | initContainers: 12 | - name: set-lustre-cache 13 | image: amazonlinux:2 14 | command: ["/bin/sh","-c"] 15 | args: ["amazon-linux-extras install lustre -y && /sbin/lctl set_param llite.*.max_cached_mb=32"] 16 | securityContext: 17 | privileged: true 18 | containers: 19 | - name: app 20 | image: amazonlinux:2 21 | command: ["/bin/sh"] 22 | args: ["-c", "sleep 999999"] 23 | volumeMounts: 24 | - name: persistent-storage 25 | mountPath: /data 26 | volumes: 27 | - name: persistent-storage 28 | persistentVolumeClaim: 29 | claimName: fsx-claim 30 | ``` 31 | The `fsx-app` pod has an init container that sets `llite.*.max_cached_mb` using `lctl`. 32 | 33 | ## Notes 34 | * The aws-fsx-csi-driver image is reused in the init container for the `lctl` command. You could chose your own container image for this purpose as long as the lustre client user space tools `lctl` is available inside the image. 35 | * The init container needs to be privileged as required by `lctl` 36 | -------------------------------------------------------------------------------- /tests/e2e/driver/fsx_csi_driver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package driver 16 | 17 | import ( 18 | "fmt" 19 | 20 | "k8s.io/api/core/v1" 21 | storagev1 "k8s.io/api/storage/v1" 22 | fsxcsidriver "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver" 23 | ) 24 | 25 | // Implement PVTestDriver interface 26 | type fsxCSIDriver struct { 27 | driverName string 28 | } 29 | 30 | // InitFSxCSIDriver returns fsxCSIDriver that implements DynamicPVTestDriver interface 31 | func InitFSxCSIDriver() PVTestDriver { 32 | return &fsxCSIDriver{ 33 | driverName: fsxcsidriver.DriverName, 34 | } 35 | } 36 | 37 | func (d *fsxCSIDriver) GetDynamicProvisionStorageClass(parameters map[string]string, mountOptions []string, reclaimPolicy *v1.PersistentVolumeReclaimPolicy, bindingMode *storagev1.VolumeBindingMode, allowedTopologyValues []string, namespace string) *storagev1.StorageClass { 38 | provisioner := d.driverName 39 | generateName := fmt.Sprintf("%s-%s-dynamic-sc-", namespace, provisioner) 40 | allowedTopologies := []v1.TopologySelectorTerm{} 41 | return getStorageClass(generateName, provisioner, parameters, mountOptions, reclaimPolicy, bindingMode, allowedTopologies) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/driver/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package driver 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "runtime" 23 | ) 24 | 25 | // These are set during build time via -ldflags 26 | var ( 27 | driverVersion string 28 | gitCommit string 29 | buildDate string 30 | ) 31 | 32 | type VersionInfo struct { 33 | DriverVersion string `json:"driverVersion"` 34 | GitCommit string `json:"gitCommit"` 35 | BuildDate string `json:"buildDate"` 36 | GoVersion string `json:"goVersion"` 37 | Compiler string `json:"compiler"` 38 | Platform string `json:"platform"` 39 | } 40 | 41 | func GetVersion() VersionInfo { 42 | return VersionInfo{ 43 | DriverVersion: driverVersion, 44 | GitCommit: gitCommit, 45 | BuildDate: buildDate, 46 | GoVersion: runtime.Version(), 47 | Compiler: runtime.Compiler, 48 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 49 | } 50 | } 51 | 52 | func GetVersionJSON() (string, error) { 53 | info := GetVersion() 54 | marshalled, err := json.MarshalIndent(&info, "", " ") 55 | if err != nil { 56 | return "", err 57 | } 58 | return string(marshalled), nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/driver/mount.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package driver 18 | 19 | import ( 20 | "k8s.io/mount-utils" 21 | "os" 22 | ) 23 | 24 | // Mounter is an interface for mount operations 25 | type Mounter interface { 26 | mount.Interface 27 | IsCorruptedMnt(err error) bool 28 | PathExists(path string) (bool, error) 29 | MakeDir(pathname string) error 30 | } 31 | 32 | type NodeMounter struct { 33 | mount.Interface 34 | } 35 | 36 | func newNodeMounter() (Mounter, error) { 37 | return &NodeMounter{ 38 | Interface: mount.New(""), 39 | }, nil 40 | } 41 | 42 | func (m *NodeMounter) MakeDir(pathname string) error { 43 | err := os.MkdirAll(pathname, os.FileMode(0755)) 44 | if err != nil { 45 | if !os.IsExist(err) { 46 | return err 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | // IsCorruptedMnt return true if err is about corrupted mount point 53 | func (m *NodeMounter) IsCorruptedMnt(err error) bool { 54 | return mount.IsCorruptedMnt(err) 55 | } 56 | 57 | func (m *NodeMounter) PathExists(path string) (bool, error) { 58 | if _, err := os.Stat(path); os.IsNotExist(err) { 59 | return false, nil 60 | } else if err != nil { 61 | return false, err 62 | } 63 | return true, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/cloud/metadata_ec2.go: -------------------------------------------------------------------------------- 1 | package cloud 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go-v2/config" 8 | "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" 9 | "k8s.io/klog/v2" 10 | ) 11 | 12 | type EC2MetadataClient func() (EC2Metadata, error) 13 | 14 | type imdsClient struct { 15 | *imds.Client 16 | } 17 | 18 | var DefaultEC2MetadataClient = func() (EC2Metadata, error) { 19 | cfg, err := config.LoadDefaultConfig(context.Background()) 20 | if err != nil { 21 | return nil, err 22 | } 23 | svc := imds.NewFromConfig(cfg) 24 | return &imdsClient{svc}, nil 25 | } 26 | 27 | func EC2MetadataInstanceInfo(svc EC2Metadata, regionFromSession string) (*Metadata, error) { 28 | doc, err := svc.GetInstanceIdentityDocument(context.Background(), nil) 29 | klog.InfoS("Retrieving EC2 instance identity Metadata", "regionFromSession", regionFromSession) 30 | if err != nil { 31 | return nil, fmt.Errorf("could not get EC2 instance identity metadata: %w", err) 32 | } 33 | 34 | if len(doc.InstanceID) == 0 { 35 | return nil, fmt.Errorf("could not get valid EC2 instance ID") 36 | } 37 | 38 | if len(doc.InstanceType) == 0 { 39 | return nil, fmt.Errorf("could not get valid EC2 instance type") 40 | } 41 | 42 | if len(doc.Region) == 0 { 43 | if len(regionFromSession) != 0 { 44 | doc.Region = regionFromSession 45 | } else { 46 | return nil, fmt.Errorf("could not get valid EC2 Region") 47 | } 48 | } 49 | 50 | if len(doc.AvailabilityZone) == 0 { 51 | return nil, fmt.Errorf("could not get valid EC2 availability zone") 52 | } 53 | 54 | instanceInfo := Metadata{ 55 | InstanceID: doc.InstanceID, 56 | InstanceType: doc.InstanceType, 57 | Region: doc.Region, 58 | AvailabilityZone: doc.AvailabilityZone, 59 | } 60 | 61 | return &instanceInfo, nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/driver/version_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package driver 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | "runtime" 23 | "testing" 24 | ) 25 | 26 | func TestGetVersion(t *testing.T) { 27 | version := GetVersion() 28 | 29 | expected := VersionInfo{ 30 | DriverVersion: "", 31 | GitCommit: "", 32 | BuildDate: "", 33 | GoVersion: runtime.Version(), 34 | Compiler: runtime.Compiler, 35 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 36 | } 37 | 38 | if !reflect.DeepEqual(version, expected) { 39 | t.Fatalf("structs not equal\ngot:\n%+v\nexpected: \n%+v", version, expected) 40 | } 41 | } 42 | func TestGetVersionJSON(t *testing.T) { 43 | version, err := GetVersionJSON() 44 | if err != nil { 45 | t.Fatalf("unexpected error: %v", err) 46 | } 47 | expected := fmt.Sprintf(`{ 48 | "driverVersion": "", 49 | "gitCommit": "", 50 | "buildDate": "", 51 | "goVersion": "%s", 52 | "compiler": "%s", 53 | "platform": "%s" 54 | }`, runtime.Version(), runtime.Compiler, fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)) 55 | 56 | if version != expected { 57 | t.Fatalf("json not equal\ngot:\n%s\nexpected:\n%s", version, expected) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/e2e/testsuites/dynamically_provisioned_cmd_volume_tester.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package testsuites 16 | 17 | import ( 18 | "k8s.io/api/core/v1" 19 | clientset "k8s.io/client-go/kubernetes" 20 | "sigs.k8s.io/aws-fsx-csi-driver/tests/e2e/driver" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | ) 24 | 25 | // DynamicallyProvisionedCmdVolumeTest will provision required StorageClass(es), PVC(s) and Pod(s) 26 | // Waiting for the PV provisioner to create a new PV 27 | // Testing if the Pod(s) Cmd is run with a 0 exit code 28 | type DynamicallyProvisionedCmdVolumeTest struct { 29 | CSIDriver driver.DynamicPVTestDriver 30 | Pods []PodDetails 31 | } 32 | 33 | func (t *DynamicallyProvisionedCmdVolumeTest) Run(client clientset.Interface, namespace *v1.Namespace) { 34 | for _, pod := range t.Pods { 35 | tpod, cleanup := pod.SetupWithDynamicVolumes(client, namespace, t.CSIDriver) 36 | // defer must be called here for resources not get removed before using them 37 | for i := range cleanup { 38 | defer cleanup[i]() 39 | } 40 | 41 | By("deploying the pod") 42 | tpod.Create() 43 | defer tpod.Cleanup() 44 | By("checking that the pods command exits with no error") 45 | tpod.WaitForSuccess() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/container-image.yaml: -------------------------------------------------------------------------------- 1 | name: Container Images 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 8 | env: 9 | REGION : "us-east-1" 10 | jobs: 11 | build: 12 | # this is to prevent the job to run at forked projects 13 | if: github.repository == 'kubernetes-sigs/aws-fsx-csi-driver' 14 | runs-on: ubuntu-latest 15 | permissions: 16 | id-token: write 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | - name: Set up Docker Buildx 21 | id: buildx 22 | uses: docker/setup-buildx-action@v1 23 | - name: Set up QEMU 24 | uses: docker/setup-qemu-action@v1 25 | - name: Configure AWS Credentials 26 | uses: aws-actions/configure-aws-credentials@master 27 | with: 28 | role-to-assume: arn:aws:iam::664215443545:role/LustreCSIDriverImagePublisherRole 29 | aws-region: ${{ env.REGION }} 30 | - name: Login to Amazon ECR Public 31 | id: login-ecr-public 32 | uses: aws-actions/amazon-ecr-login@v1 33 | with: 34 | registry-type: public 35 | - name: Build, tag, and push docker image to Amazon ECR Public Repository 36 | env: 37 | REGISTRY: ${{ steps.login-ecr-public.outputs.registry }} 38 | REGISTRY_ALIAS: fsx-csi-driver 39 | REPOSITORY: aws-fsx-csi-driver 40 | run: | 41 | BRANCH_OR_TAG=$(echo $GITHUB_REF | cut -d'/' -f3) 42 | if [ "$BRANCH_OR_TAG" = "master" ]; then 43 | GIT_TAG=$GITHUB_SHA 44 | else 45 | GIT_TAG=$BRANCH_OR_TAG 46 | fi 47 | export REGISTRY=$REGISTRY/$REGISTRY_ALIAS 48 | export TAG=$GIT_TAG 49 | make all-push 50 | -------------------------------------------------------------------------------- /hack/update-gomock: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | set -euo pipefail 18 | 19 | IMPORT_PATH=sigs.k8s.io/aws-fsx-csi-driver 20 | mockgen -package=mocks -destination=./pkg/driver/mocks/mock_mount.go --build_flags=--mod=mod ${IMPORT_PATH}/pkg/driver Mounter 21 | mockgen -package=mocks -destination=./pkg/cloud/mocks/mock_ec2metadata.go --build_flags=--mod=mod ${IMPORT_PATH}/pkg/cloud EC2Metadata 22 | mockgen -package=mocks -destination=./pkg/cloud/mocks/mock_metadata.go --build_flags=--mod=mod ${IMPORT_PATH}/pkg/cloud MetadataService 23 | 24 | mockgen -package=mocks -destination=./pkg/cloud/mocks/mock_fsx.go --build_flags=--mod=mod ${IMPORT_PATH}/pkg/cloud FSx 25 | mockgen -package=mocks -destination=./pkg/driver/mocks/mock_cloud.go --build_flags=--mod=mod ${IMPORT_PATH}/pkg/cloud Cloud 26 | 27 | # Reflection-based mocking for external dependencies 28 | mockgen -package=mocks -destination=./pkg/driver/mocks/mock_k8s_client.go --build_flags=--mod=mod -mock_names='Interface=MockKubernetesClient' k8s.io/client-go/kubernetes Interface 29 | mockgen -package=mocks -destination=./pkg/driver/mocks/mock_k8s_corev1.go --build_flags=--mod=mod k8s.io/client-go/kubernetes/typed/core/v1 CoreV1Interface,NodeInterface,PodInterface,PersistentVolumeInterface,PersistentVolumeClaimInterface 30 | -------------------------------------------------------------------------------- /tests/sanity/sanity_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package sanity 15 | 16 | import ( 17 | "os" 18 | "testing" 19 | 20 | . "github.com/onsi/ginkgo" 21 | . "github.com/onsi/gomega" 22 | 23 | sanity "github.com/kubernetes-csi/csi-test/pkg/sanity" 24 | 25 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver" 26 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/util" 27 | ) 28 | 29 | const ( 30 | mountPath = "/tmp/csi/mount" 31 | stagePath = "/tmp/csi/stage" 32 | socket = "/tmp/csi.sock" 33 | endpoint = "unix://" + socket 34 | ) 35 | 36 | var fsxDriver *driver.Driver 37 | 38 | func TestSanity(t *testing.T) { 39 | RegisterFailHandler(Fail) 40 | RunSpecs(t, "Sanity Tests Suite") 41 | } 42 | 43 | var _ = BeforeSuite(func() { 44 | fsxDriver = driver.NewFakeDriver(endpoint) 45 | go func() { 46 | Expect(fsxDriver.Run()).NotTo(HaveOccurred()) 47 | }() 48 | }) 49 | 50 | var _ = AfterSuite(func() { 51 | fsxDriver.Stop() 52 | Expect(os.RemoveAll(socket)).NotTo(HaveOccurred()) 53 | }) 54 | 55 | var _ = Describe("AWS FSx for Lustre CSI Driver", func() { 56 | _ = os.MkdirAll("/tmp/csi", os.ModePerm) 57 | config := &sanity.Config{ 58 | Address: endpoint, 59 | TargetPath: mountPath, 60 | StagingPath: stagePath, 61 | TestVolumeSize: 2000 * util.GiB, 62 | } 63 | sanity.GinkgoTest(config) 64 | }) 65 | -------------------------------------------------------------------------------- /pkg/driver/identity.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package driver 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/container-storage-interface/spec/lib/go/csi" 23 | ) 24 | 25 | func (d *Driver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { 26 | resp := &csi.GetPluginInfoResponse{ 27 | Name: DriverName, 28 | VendorVersion: driverVersion, 29 | } 30 | 31 | return resp, nil 32 | } 33 | 34 | func (d *Driver) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { 35 | resp := &csi.GetPluginCapabilitiesResponse{ 36 | Capabilities: []*csi.PluginCapability{ 37 | { 38 | Type: &csi.PluginCapability_Service_{ 39 | Service: &csi.PluginCapability_Service{ 40 | Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, 41 | }, 42 | }, 43 | }, 44 | { 45 | Type: &csi.PluginCapability_VolumeExpansion_{ 46 | VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ 47 | Type: csi.PluginCapability_VolumeExpansion_ONLINE, 48 | }, 49 | }, 50 | }, 51 | }, 52 | } 53 | 54 | return resp, nil 55 | } 56 | 57 | func (d *Driver) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { 58 | return &csi.ProbeResponse{}, nil 59 | } 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ARG AL_VERSION=al23 16 | FROM --platform=$BUILDPLATFORM golang:1.24 as builder 17 | WORKDIR /go/src/github.com/kubernetes-sigs/aws-fsx-csi-driver 18 | COPY go.* . 19 | RUN go mod download 20 | COPY . . 21 | ARG TARGETOS 22 | ARG TARGETARCH 23 | RUN OS=$TARGETOS ARCH=$TARGETARCH make $TARGETOS/$TARGETARCH 24 | 25 | # https://github.com/aws/eks-distro-build-tooling/blob/main/eks-distro-base/Dockerfile.minimal-base-csi-ebs#L36 26 | FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-csi-ebs-builder:latest-${AL_VERSION} as rpm-installer 27 | ARG AL_VERSION 28 | # shallow install systemd and the kernel which are not needed in the final container image 29 | # since lustre is not run as a systemd service and the kernel module is node loaded via the container 30 | # to avoid pulling in a large tree of unnecessary dependencies 31 | RUN set -x && \ 32 | enable_extra lustre && \ 33 | clean_install "kernel systemd" true true; \ 34 | if [[ "${AL_VERSION}" == "al23" ]]; then \ 35 | clean_install lustre-client; \ 36 | else \ 37 | clean_install lustre; \ 38 | fi; \ 39 | remove_package "kernel systemd" true && \ 40 | cleanup "fsx-csi" 41 | 42 | FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-csi-ebs:latest-${AL_VERSION} AS linux-amazon 43 | 44 | COPY --from=rpm-installer /newroot / 45 | 46 | COPY --from=builder /go/src/github.com/kubernetes-sigs/aws-fsx-csi-driver/bin/aws-fsx-csi-driver /bin/aws-fsx-csi-driver 47 | COPY THIRD-PARTY / 48 | 49 | ENTRYPOINT ["/bin/aws-fsx-csi-driver"] 50 | -------------------------------------------------------------------------------- /tests/e2e/driver/driver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package driver 16 | 17 | import ( 18 | "k8s.io/api/core/v1" 19 | storagev1 "k8s.io/api/storage/v1" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | type PVTestDriver interface { 24 | DynamicPVTestDriver 25 | } 26 | 27 | // DynamicPVTestDriver represents an interface for a CSI driver that supports DynamicPV 28 | type DynamicPVTestDriver interface { 29 | // GetDynamicProvisionStorageClass returns a StorageClass dynamic provision Persistent Volume 30 | GetDynamicProvisionStorageClass(parameters map[string]string, mountOptions []string, reclaimPolicy *v1.PersistentVolumeReclaimPolicy, bindingMode *storagev1.VolumeBindingMode, allowedTopologyValues []string, namespace string) *storagev1.StorageClass 31 | } 32 | 33 | func getStorageClass( 34 | generateName string, 35 | provisioner string, 36 | parameters map[string]string, 37 | mountOptions []string, 38 | reclaimPolicy *v1.PersistentVolumeReclaimPolicy, 39 | bindingMode *storagev1.VolumeBindingMode, 40 | allowedTopologies []v1.TopologySelectorTerm, 41 | ) *storagev1.StorageClass { 42 | if reclaimPolicy == nil { 43 | defaultReclaimPolicy := v1.PersistentVolumeReclaimDelete 44 | reclaimPolicy = &defaultReclaimPolicy 45 | } 46 | if bindingMode == nil { 47 | defaultBindingMode := storagev1.VolumeBindingImmediate 48 | bindingMode = &defaultBindingMode 49 | } 50 | return &storagev1.StorageClass{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | GenerateName: generateName, 53 | }, 54 | Provisioner: provisioner, 55 | Parameters: parameters, 56 | MountOptions: mountOptions, 57 | ReclaimPolicy: reclaimPolicy, 58 | VolumeBindingMode: bindingMode, 59 | AllowedTopologies: allowedTopologies, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/kubernetes/multiple_pods/README.md: -------------------------------------------------------------------------------- 1 | ## Multiple Pods Read Write Many 2 | This example shows how to create a dynamically provisioned FSx for Lustre PV and access it from multiple pods with `ReadWriteMany` access mode. If you are using static provisioning, following steps to setup static provisioned PV with access mode set to `ReadWriteMany` and the rest of steps of consuming the volume from pods are similar. 3 | 4 | ### Edit [StorageClass](./specs/storageclass.yaml) 5 | ``` 6 | kind: StorageClass 7 | apiVersion: storage.k8s.io/v1 8 | metadata: 9 | name: fsx-sc 10 | provisioner: fsx.csi.aws.com 11 | parameters: 12 | subnetId: subnet-056da83524edbe641 13 | securityGroupIds: sg-086f61ea73388fb6b 14 | deploymentType: SCRATCH_2 15 | ``` 16 | * subnetId - the subnet ID that the FSx for Lustre filesystem should be created inside. 17 | * securityGroupIds - a comma-separated list of security group IDs that should be attached to the filesystem 18 | * deploymentType (Optional) - FSx for Lustre supports four deployment types, SCRATCH_1, SCRATCH_2, PERSISTENT_1 and PERSISTENT_2. Default: SCRATCH_1. 19 | * kmsKeyId (Optional) - for deployment types PERSISTENT_1 and PERSISTENT_2, customer can specify a KMS key to use. 20 | * perUnitStorageThroughput (Optional) - for deployment type PERSISTENT_1 and PERSISTENT_2, customer can specify the storage throughput. Default: "200". Note that customer has to specify as a string here like "200" or "100" etc. 21 | 22 | ### Deploy the Application 23 | Create PV, persistence volume claim (PVC), storageclass and the pods that consume the PV: 24 | ```sh 25 | >> kubectl apply -f examples/kubernetes/multiple_pods/specs/storageclass.yaml 26 | >> kubectl apply -f examples/kubernetes/multiple_pods/specs/claim.yaml 27 | >> kubectl apply -f examples/kubernetes/multiple_pods/specs/pod1.yaml 28 | >> kubectl apply -f examples/kubernetes/multiple_pods/specs/pod2.yaml 29 | ``` 30 | 31 | Both pod1 and pod2 are writing to the same FSx for Lustre filesystem at the same time. 32 | 33 | ### Check the Application uses FSx for Lustre filesystem 34 | After the objects are created, verify that pod is running: 35 | 36 | ```sh 37 | >> kubectl get pods 38 | ``` 39 | 40 | Also verify that data is written onto FSx for Luster filesystem: 41 | 42 | ```sh 43 | >> kubectl exec -ti app1 -- tail -f /data/out1.txt 44 | >> kubectl exec -ti app2 -- tail -f /data/out2.txt 45 | ``` 46 | -------------------------------------------------------------------------------- /pkg/driver/internal/inflight.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package internal 17 | 18 | import ( 19 | "sync" 20 | 21 | "k8s.io/klog/v2" 22 | ) 23 | 24 | // Idempotent is the interface required to manage in flight requests. 25 | type Idempotent interface { 26 | // String The CSI data types are generated using a protobuf. 27 | // The generated structures are guaranteed to implement the Stringer interface. 28 | // Example: https://github.com/container-storage-interface/spec/blob/master/lib/go/csi/csi.pb.go#L3508 29 | // We can use the generated string as the key of our internal inflight database of requests. 30 | String() string 31 | } 32 | 33 | const ( 34 | VolumeOperationAlreadyExistsErrorMsg = "An operation with the given volume %s already exists" 35 | ) 36 | 37 | // InFlight is a struct used to manage in flight requests 38 | type InFlight struct { 39 | mux *sync.Mutex 40 | inFlight map[string]bool 41 | } 42 | 43 | // NewInFlight instantiates an InFlight structure. 44 | func NewInFlight() *InFlight { 45 | return &InFlight{ 46 | mux: &sync.Mutex{}, 47 | inFlight: make(map[string]bool), 48 | } 49 | } 50 | 51 | // Insert inserts the entry to the current list of inflight requests. 52 | // Returns false if the key already exists. 53 | func (db *InFlight) Insert(key string) bool { 54 | db.mux.Lock() 55 | defer db.mux.Unlock() 56 | 57 | _, ok := db.inFlight[key] 58 | if ok { 59 | return false 60 | } 61 | 62 | db.inFlight[key] = true 63 | return true 64 | } 65 | 66 | // Delete removes the entry from the inFlight entries map. 67 | // It doesn't return anything, and will do nothing if the specified key doesn't exist. 68 | func (db *InFlight) Delete(key string) { 69 | db.mux.Lock() 70 | defer db.mux.Unlock() 71 | 72 | delete(db.inFlight, key) 73 | klog.V(4).InfoS("Volume operation finished", "key", key) 74 | } 75 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "aws-fsx-csi-driver.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "aws-fsx-csi-driver.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "aws-fsx-csi-driver.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "aws-fsx-csi-driver.labels" -}} 38 | {{ include "aws-fsx-csi-driver.selectorLabels" . }} 39 | {{- if ne .Release.Name "kustomize" }} 40 | helm.sh/chart: {{ include "aws-fsx-csi-driver.chart" . }} 41 | {{- if .Chart.AppVersion }} 42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 43 | {{- end }} 44 | app.kubernetes.io/managed-by: {{ .Release.Service }} 45 | {{- end }} 46 | {{- end -}} 47 | 48 | {{/* 49 | Common selector labels 50 | */}} 51 | {{- define "aws-fsx-csi-driver.selectorLabels" -}} 52 | app.kubernetes.io/name: {{ include "aws-fsx-csi-driver.name" . }} 53 | {{- if ne .Release.Name "kustomize" }} 54 | app.kubernetes.io/instance: {{ .Release.Name }} 55 | {{- end }} 56 | {{- end -}} 57 | 58 | {{/* 59 | Prepare the `--extra-tags` controller flag from a map. 60 | */}} 61 | {{- define "aws-fsx-csi-driver.extra-tags" -}} 62 | {{- $extraTags := list -}} 63 | {{- range $key, $value := .Values.controller.extraTags -}} 64 | {{- $extraTags = printf "%s=%v" $key $value | append $extraTags -}} 65 | {{- end -}} 66 | {{- if $extraTags -}} 67 | {{- printf "- \"--extra-tags=%s\"" (join "," $extraTags) -}} 68 | {{- end -}} 69 | {{- end -}} 70 | -------------------------------------------------------------------------------- /tests/e2e/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package e2e 16 | 17 | import ( 18 | "flag" 19 | "log" 20 | "math/rand" 21 | "os" 22 | "path/filepath" 23 | "testing" 24 | "time" 25 | 26 | . "github.com/onsi/ginkgo/v2" 27 | . "github.com/onsi/gomega" 28 | 29 | "k8s.io/kubernetes/test/e2e/framework" 30 | frameworkconfig "k8s.io/kubernetes/test/e2e/framework/config" 31 | "k8s.io/kubernetes/test/e2e/framework/testfiles" 32 | ) 33 | 34 | const kubeconfigEnvVar = "KUBECONFIG" 35 | 36 | var clusterName = flag.String("cluster-name", "", "the cluster name") 37 | var region = flag.String("region", "us-west-2", "the region") 38 | 39 | func init() { 40 | rand.Seed(time.Now().UTC().UnixNano()) 41 | testing.Init() 42 | // k8s.io/kubernetes/test/e2e/framework requires env KUBECONFIG to be set 43 | // it does not fall back to defaults 44 | if os.Getenv(kubeconfigEnvVar) == "" { 45 | kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config") 46 | os.Setenv(kubeconfigEnvVar, kubeconfig) 47 | } 48 | 49 | framework.AfterReadingAllFlags(&framework.TestContext) 50 | // PWD is test/e2e inside the git repo 51 | testfiles.AddFileSource(testfiles.RootFileSource{Root: "../.."}) 52 | 53 | frameworkconfig.CopyFlags(frameworkconfig.Flags, flag.CommandLine) 54 | framework.RegisterCommonFlags(flag.CommandLine) 55 | framework.RegisterClusterFlags(flag.CommandLine) 56 | // framework.ClaimProvisionTimeout = 7 * time.Minute 57 | flag.Parse() 58 | } 59 | 60 | func TestE2E(t *testing.T) { 61 | RegisterFailHandler(Fail) 62 | 63 | // Run tests through the Ginkgo runner with output to console + JUnit for Jenkins 64 | var r []Reporter 65 | if framework.TestContext.ReportDir != "" { 66 | if err := os.MkdirAll(framework.TestContext.ReportDir, 0755); err != nil { 67 | log.Fatalf("Failed creating report directory: %v", err) 68 | } 69 | } 70 | 71 | RunSpecsWithDefaultAndCustomReporters(t, "AWS FSx CSI Driver End-to-End Tests", r) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/cloud/metadata_k8s.go: -------------------------------------------------------------------------------- 1 | package cloud 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "strings" 9 | 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/kubernetes" 12 | "k8s.io/client-go/rest" 13 | ) 14 | 15 | type KubernetesAPIClient func() (kubernetes.Interface, error) 16 | 17 | const eksHybridPrefix = "eks-hybrid:///" 18 | 19 | var DefaultKubernetesAPIClient = func() (kubernetes.Interface, error) { 20 | // creates the in-cluster config 21 | config, err := rest.InClusterConfig() 22 | if err != nil { 23 | return nil, err 24 | } 25 | // creates the clientset 26 | clientset, err := kubernetes.NewForConfig(config) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return clientset, nil 31 | } 32 | 33 | func KubernetesAPIInstanceInfo(clientset kubernetes.Interface) (*Metadata, error) { 34 | nodeName := os.Getenv("CSI_NODE_NAME") 35 | if nodeName == "" { 36 | return nil, fmt.Errorf("CSI_NODE_NAME env var not set") 37 | } 38 | 39 | // get node with k8s API 40 | node, err := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) 41 | if err != nil { 42 | return nil, fmt.Errorf("error getting Node %v: %w", nodeName, err) 43 | } 44 | 45 | providerID := node.Spec.ProviderID 46 | if providerID == "" { 47 | return nil, fmt.Errorf("node providerID empty, cannot parse") 48 | } 49 | 50 | if strings.HasPrefix(providerID, eksHybridPrefix) { 51 | return metadataForHybridNode(providerID) 52 | } 53 | 54 | // if not hybrid, assume AWS EC2 55 | return metadataForEC2Node(providerID) 56 | } 57 | 58 | func metadataForEC2Node(providerID string) (*Metadata, error) { 59 | awsInstanceIDRegex := "s\\.i-[a-z0-9]+|i-[a-z0-9]+$" 60 | 61 | re := regexp.MustCompile(awsInstanceIDRegex) 62 | instanceID := re.FindString(providerID) 63 | if instanceID == "" { 64 | return nil, fmt.Errorf("did not find aws instance ID in node providerID string") 65 | } 66 | 67 | instanceInfo := Metadata{ 68 | InstanceID: instanceID, 69 | } 70 | 71 | return &instanceInfo, nil 72 | } 73 | 74 | func metadataForHybridNode(providerID string) (*Metadata, error) { 75 | // provider ID for hybrid node is in formt eks:///region/clustername/instanceid 76 | info := strings.TrimPrefix(providerID, eksHybridPrefix) 77 | 78 | parts := strings.Split(info, "/") 79 | if len(parts) != 3 { 80 | return nil, fmt.Errorf("invalid hybrid node providerID format") 81 | } 82 | 83 | instanceInfo := Metadata{ 84 | InstanceID: parts[2], 85 | Region: parts[0], 86 | } 87 | return &instanceInfo, nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/driver/internal/inflight_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package internal 17 | 18 | import ( 19 | "testing" 20 | ) 21 | 22 | type testRequest struct { 23 | volumeId string 24 | expResp bool 25 | delete bool 26 | } 27 | 28 | func TestInFlight(t *testing.T) { 29 | testCases := []struct { 30 | name string 31 | requests []testRequest 32 | }{ 33 | { 34 | name: "success normal", 35 | requests: []testRequest{ 36 | { 37 | 38 | volumeId: "random-vol-name", 39 | expResp: true, 40 | }, 41 | }, 42 | }, 43 | { 44 | name: "success adding request with different volumeId", 45 | requests: []testRequest{ 46 | { 47 | volumeId: "random-vol-foobar", 48 | expResp: true, 49 | }, 50 | { 51 | volumeId: "random-vol-name-foobar", 52 | expResp: true, 53 | }, 54 | }, 55 | }, 56 | { 57 | name: "failed adding request with same volumeId", 58 | requests: []testRequest{ 59 | { 60 | volumeId: "random-vol-name-foobar", 61 | expResp: true, 62 | }, 63 | { 64 | volumeId: "random-vol-name-foobar", 65 | expResp: false, 66 | }, 67 | }, 68 | }, 69 | 70 | { 71 | name: "success add, delete, add copy", 72 | requests: []testRequest{ 73 | { 74 | volumeId: "random-vol-name", 75 | expResp: true, 76 | }, 77 | { 78 | volumeId: "random-vol-name", 79 | expResp: false, 80 | delete: true, 81 | }, 82 | { 83 | volumeId: "random-vol-name", 84 | expResp: true, 85 | }, 86 | }, 87 | }, 88 | } 89 | 90 | for _, tc := range testCases { 91 | t.Run(tc.name, func(t *testing.T) { 92 | db := NewInFlight() 93 | for _, r := range tc.requests { 94 | var resp bool 95 | if r.delete { 96 | db.Delete(r.volumeId) 97 | } else { 98 | resp = db.Insert(r.volumeId) 99 | } 100 | if r.expResp != resp { 101 | t.Fatalf("expected insert to be %+v, got %+v", r.expResp, resp) 102 | } 103 | } 104 | }) 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tester/e2e-test-config.yaml: -------------------------------------------------------------------------------- 1 | cluster: 2 | kops: 3 | stateFile: s3://k8s-kops-csi-shared-e2e 4 | zones: us-west-2a 5 | nodeCount: 3 6 | nodeSize: c5.large 7 | kubernetesVersion: 1.15.3 8 | iamPolicies: |2 9 | additionalPolicies: 10 | node: | 11 | [ 12 | { 13 | "Effect": "Allow", 14 | "Action": [ 15 | "iam:CreateServiceLinkedRole", 16 | "iam:AttachRolePolicy", 17 | "iam:PutRolePolicy" 18 | ], 19 | "Resource": "arn:aws:iam::*:role/aws-service-role/s3.data-source.lustre.fsx.amazonaws.com/*" 20 | }, 21 | { 22 | "Effect": "Allow", 23 | "Action": [ 24 | "s3:ListBucket", 25 | "fsx:CreateFileSystem", 26 | "fsx:DeleteFileSystem", 27 | "fsx:DescribeFileSystems" 28 | ], 29 | "Resource": ["*"] 30 | } 31 | ] 32 | 33 | build: | 34 | eval $(aws ecr get-login --region us-west-2 --no-include-email) 35 | AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) 36 | IMAGE_TAG={{TEST_ID}} 37 | IMAGE_NAME=$AWS_ACCOUNT_ID.dkr.ecr.us-west-2.amazonaws.com/aws-fsx-csi-driver 38 | docker build -t $IMAGE_NAME:$IMAGE_TAG . 39 | docker push $IMAGE_NAME:$IMAGE_TAG 40 | 41 | install: | 42 | HELM_VERSION=3.1.1 43 | 44 | echo "Installing Helm version: ${HELM_VERSION}" 45 | wget "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" 46 | tar -zxvf "helm-v${HELM_VERSION}-linux-amd64.tar.gz" 47 | mv linux-amd64/helm /usr/local/bin/helm 48 | 49 | echo "Deploying driver" 50 | AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) 51 | IMAGE_TAG={{TEST_ID}} 52 | IMAGE_NAME=$AWS_ACCOUNT_ID.dkr.ecr.us-west-2.amazonaws.com/aws-fsx-csi-driver 53 | 54 | helm upgrade aws-fsx-csi-driver \ 55 | --install helm/ \ 56 | --namespace kube-system \ 57 | -f helm/values.yaml \ 58 | --set controllerService.fsxPlugin.image.repository=$IMAGE_NAME \ 59 | --set controllerService.fsxPlugin.image.tag=$IMAGE_TAG \ 60 | --set nodeService.fsxPlugin.image.repository=$IMAGE_NAME \ 61 | --set nodeService.fsxPlugin.image.tag=$IMAGE_TAG 62 | 63 | uninstall: | 64 | echo "Removing driver" 65 | helm delete aws-fsx-csi-driver --namespace kube-system 66 | 67 | test: | 68 | go install github.com/onsi/ginkgo/v2/ginkgo 69 | export KUBECONFIG=$HOME/.kube/config 70 | cluster_name=test-cluster-{{TEST_ID}}.k8s.local 71 | skip="\[Disruptive\]\ 72 | |subPath.should.be.able.to.unmount.after.the.subpath.directory.is.deleted\ 73 | |should.not.mount./.map.unused.volumes.in.a.pod" 74 | ginkgo -timeout 24h -p -nodes=8 -skip=$skip -v ./tests/e2e -- --cluster-name=$cluster_name --region=us-west-2 --report-dir=$ARTIFACTS 75 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Helm chart 2 | 3 | # v1.15.0 4 | * Use driver image 1.8.0 5 | 6 | # v1.14.0 7 | * Use driver image 1.7.0 8 | 9 | # v1.13.0 10 | * Use driver image 1.6.0 11 | 12 | # v1.12.1 13 | * Parameterize controller.extraTags 14 | 15 | # v1.12.0 16 | * Use driver image 1.5.0 17 | 18 | # v1.11.0 19 | * Use driver image 1.4.0 20 | 21 | # v1.10.0 22 | * Use driver image 1.3.0 23 | 24 | # v1.9.2 25 | * Fix ci tooling 26 | 27 | # v1.9.1 28 | * Add affinity to Daemonset 29 | 30 | # v1.9.0 31 | * Use driver image 1.2.0 32 | 33 | # v1.8.0 34 | * Use driver image 1.1.0 35 | 36 | # v1.7.0 37 | * Use driver image 1.0.0 38 | 39 | # v1.6.1 40 | * Removed hostNetwork: true from helm deployment 41 | * Allow for extra tags in controller deployment 42 | * Add region for controller to helm chart 43 | 44 | # v1.6.0 45 | * Use driver image 0.10.0 46 | * Add driver modes for controller and node pods 47 | * Allow for json logging 48 | * parametrized pod tolerations 49 | * Added support for startup taint (please see install documentation for more information) 50 | 51 | # v1.5.1 52 | * Support controller pod annotations in helm chart 53 | * Support node pod annotations in helm chart 54 | 55 | # v1.5.0 56 | * Use driver 0.9.0 57 | 58 | # v1.4.4 59 | * Use driver 0.8.3 60 | 61 | # v1.4.3 62 | * Added option to configure `fsGroupPolicy` on the CSIDriver object. Adding such a configuration allows kubelet to change ownership of every file in the volume at mount time. 63 | Documentation on fsGroupPolicy can be found [here](https://kubernetes-csi.github.io/docs/support-fsgroup.html). 64 | 65 | **Side-note**: Setting fsGroupPolicy to `File` in for configurations that mount the disk on multiple nodes as the same time can lead to race-conditions and subsequently deadlocks, unless if **every** Pod mounting the volume has the same *securityContext* which includes the setting `fsGroupChangePolicy: "OnRootMismatch"` 66 | 67 | # v1.4.2 68 | * Use driver 0.8.2 69 | 70 | # v1.4.1 71 | * Use driver 0.8.1 72 | 73 | # v1.4.0 74 | * Use driver 0.8.0 75 | 76 | # v1.3.2 77 | * Update ECR sidecars to 1-18-13 78 | 79 | # v1.3.1 80 | * Use driver 0.7.1 81 | 82 | # v1.3.0 83 | * Use driver 0.7.0 84 | 85 | # v1.2.0 86 | * Use driver 0.6.0 87 | * Add sidecar for storage scaling (external-resizer) 88 | 89 | # v1.1.0 90 | * Use driver 0.5.0 91 | 92 | # v1.0.0 93 | * Remove support for Helm 2 94 | * Reorganize values to be more consistent with EFS and EBS helm charts 95 | * controllerService -> controller 96 | * nodeService -> node 97 | * Add node.serviceAccount 98 | * Add dnsPolicy and dnsConfig 99 | * Add imagePullSecrets 100 | * Add controller.tolerations, node.tolerations, and node.tolerateAllTaints 101 | * Remove extraArgs, securityContext, podSecurityContext 102 | * Bump sidecar images to support kubernetes >=1.20 103 | * Require kubernetes >=1.17 104 | -------------------------------------------------------------------------------- /pkg/cloud/metadata.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloud 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/klog/v2" 24 | 25 | "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" 26 | ) 27 | 28 | // MetadataService represents AWS metadata service. 29 | type MetadataService interface { 30 | GetInstanceID() string 31 | GetInstanceType() string 32 | GetRegion() string 33 | GetAvailabilityZone() string 34 | } 35 | 36 | type EC2Metadata interface { 37 | // ec2 instance metadata endpoints: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html 38 | GetMetadata(context.Context, *imds.GetMetadataInput, ...func(*imds.Options)) (*imds.GetMetadataOutput, error) 39 | GetInstanceIdentityDocument(context.Context, *imds.GetInstanceIdentityDocumentInput, ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) 40 | } 41 | 42 | type Metadata struct { 43 | InstanceID string 44 | InstanceType string 45 | Region string 46 | AvailabilityZone string 47 | } 48 | 49 | var _ MetadataService = &Metadata{} 50 | 51 | // GetInstanceID returns the instance identification. 52 | func (m *Metadata) GetInstanceID() string { 53 | return m.InstanceID 54 | } 55 | 56 | // GetInstanceType returns the instance type. 57 | func (m *Metadata) GetInstanceType() string { 58 | return m.InstanceType 59 | } 60 | 61 | // GetRegion returns the Region Zone which the instance is in. 62 | func (m *Metadata) GetRegion() string { 63 | return m.Region 64 | } 65 | 66 | // GetAvailabilityZone returns the Availability Zone which the instance is in. 67 | func (m *Metadata) GetAvailabilityZone() string { 68 | return m.AvailabilityZone 69 | } 70 | 71 | // NewMetadataService returns a new MetadataServiceImplementation. 72 | func NewMetadataService(ec2MetadataClient EC2MetadataClient, k8sAPIClient KubernetesAPIClient, region string) (MetadataService, error) { 73 | klog.InfoS("retrieving instance data from ec2 metadata") 74 | svc, err := ec2MetadataClient() 75 | if err != nil { 76 | klog.InfoS("error creating ec2 metadata client", "err", err) 77 | } else { 78 | metadataInfo, err := EC2MetadataInstanceInfo(svc, region) 79 | if err == nil { 80 | return metadataInfo, nil 81 | } 82 | klog.InfoS("error retrieving instance data from ec2 metadata", "err", err) 83 | } 84 | 85 | klog.InfoS("retrieving instance data from kubernetes api") 86 | clientset, err := k8sAPIClient() 87 | if err != nil { 88 | klog.InfoS("error creating kubernetes api client", "err", err) 89 | } else { 90 | klog.InfoS("kubernetes api is available") 91 | return KubernetesAPIInstanceInfo(clientset) 92 | } 93 | 94 | return nil, fmt.Errorf("error getting instance data from ec2 metadata or kubernetes api") 95 | } 96 | -------------------------------------------------------------------------------- /pkg/cloud/mocks/mock_ec2metadata.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud (interfaces: EC2Metadata) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -package=mocks -destination=./pkg/cloud/mocks/mock_ec2metadata.go --build_flags=--mod=mod sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud EC2Metadata 7 | // 8 | 9 | // Package mocks is a generated GoMock package. 10 | package mocks 11 | 12 | import ( 13 | context "context" 14 | reflect "reflect" 15 | 16 | imds "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" 17 | gomock "go.uber.org/mock/gomock" 18 | ) 19 | 20 | // MockEC2Metadata is a mock of EC2Metadata interface. 21 | type MockEC2Metadata struct { 22 | ctrl *gomock.Controller 23 | recorder *MockEC2MetadataMockRecorder 24 | isgomock struct{} 25 | } 26 | 27 | // MockEC2MetadataMockRecorder is the mock recorder for MockEC2Metadata. 28 | type MockEC2MetadataMockRecorder struct { 29 | mock *MockEC2Metadata 30 | } 31 | 32 | // NewMockEC2Metadata creates a new mock instance. 33 | func NewMockEC2Metadata(ctrl *gomock.Controller) *MockEC2Metadata { 34 | mock := &MockEC2Metadata{ctrl: ctrl} 35 | mock.recorder = &MockEC2MetadataMockRecorder{mock} 36 | return mock 37 | } 38 | 39 | // EXPECT returns an object that allows the caller to indicate expected use. 40 | func (m *MockEC2Metadata) EXPECT() *MockEC2MetadataMockRecorder { 41 | return m.recorder 42 | } 43 | 44 | // GetInstanceIdentityDocument mocks base method. 45 | func (m *MockEC2Metadata) GetInstanceIdentityDocument(arg0 context.Context, arg1 *imds.GetInstanceIdentityDocumentInput, arg2 ...func(*imds.Options)) (*imds.GetInstanceIdentityDocumentOutput, error) { 46 | m.ctrl.T.Helper() 47 | varargs := []any{arg0, arg1} 48 | for _, a := range arg2 { 49 | varargs = append(varargs, a) 50 | } 51 | ret := m.ctrl.Call(m, "GetInstanceIdentityDocument", varargs...) 52 | ret0, _ := ret[0].(*imds.GetInstanceIdentityDocumentOutput) 53 | ret1, _ := ret[1].(error) 54 | return ret0, ret1 55 | } 56 | 57 | // GetInstanceIdentityDocument indicates an expected call of GetInstanceIdentityDocument. 58 | func (mr *MockEC2MetadataMockRecorder) GetInstanceIdentityDocument(arg0, arg1 any, arg2 ...any) *gomock.Call { 59 | mr.mock.ctrl.T.Helper() 60 | varargs := append([]any{arg0, arg1}, arg2...) 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceIdentityDocument", reflect.TypeOf((*MockEC2Metadata)(nil).GetInstanceIdentityDocument), varargs...) 62 | } 63 | 64 | // GetMetadata mocks base method. 65 | func (m *MockEC2Metadata) GetMetadata(arg0 context.Context, arg1 *imds.GetMetadataInput, arg2 ...func(*imds.Options)) (*imds.GetMetadataOutput, error) { 66 | m.ctrl.T.Helper() 67 | varargs := []any{arg0, arg1} 68 | for _, a := range arg2 { 69 | varargs = append(varargs, a) 70 | } 71 | ret := m.ctrl.Call(m, "GetMetadata", varargs...) 72 | ret0, _ := ret[0].(*imds.GetMetadataOutput) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetMetadata indicates an expected call of GetMetadata. 78 | func (mr *MockEC2MetadataMockRecorder) GetMetadata(arg0, arg1 any, arg2 ...any) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | varargs := append([]any{arg0, arg1}, arg2...) 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetadata", reflect.TypeOf((*MockEC2Metadata)(nil).GetMetadata), varargs...) 82 | } 83 | -------------------------------------------------------------------------------- /tests/e2e/testsuites/specs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package testsuites 16 | 17 | import ( 18 | "fmt" 19 | 20 | "sigs.k8s.io/aws-fsx-csi-driver/tests/e2e/driver" 21 | 22 | "k8s.io/api/core/v1" 23 | storagev1 "k8s.io/api/storage/v1" 24 | clientset "k8s.io/client-go/kubernetes" 25 | 26 | . "github.com/onsi/ginkgo/v2" 27 | ) 28 | 29 | type PodDetails struct { 30 | Cmd string 31 | Volumes []VolumeDetails 32 | } 33 | 34 | type VolumeDetails struct { 35 | // Volume parameters used to config storageclass 36 | Parameters map[string]string 37 | MountOptions []string 38 | ClaimSize string 39 | ReclaimPolicy *v1.PersistentVolumeReclaimPolicy 40 | VolumeBindingMode *storagev1.VolumeBindingMode 41 | VolumeMount VolumeMountDetails 42 | } 43 | 44 | type VolumeMountDetails struct { 45 | NameGenerate string 46 | MountPathGenerate string 47 | ReadOnly bool 48 | } 49 | 50 | type VolumeDeviceDetails struct { 51 | NameGenerate string 52 | DevicePath string 53 | } 54 | 55 | func (pod *PodDetails) SetupWithDynamicVolumes(client clientset.Interface, namespace *v1.Namespace, csiDriver driver.DynamicPVTestDriver) (*TestPod, []func()) { 56 | tpod := NewTestPod(client, namespace, pod.Cmd) 57 | cleanupFuncs := make([]func(), 0) 58 | for n, v := range pod.Volumes { 59 | tpvc, funcs := v.SetupDynamicPersistentVolumeClaim(client, namespace, csiDriver) 60 | cleanupFuncs = append(cleanupFuncs, funcs...) 61 | 62 | tpod.SetupVolume(tpvc.persistentVolumeClaim, fmt.Sprintf("%s%d", v.VolumeMount.NameGenerate, n+1), fmt.Sprintf("%s%d", v.VolumeMount.MountPathGenerate, n+1), v.VolumeMount.ReadOnly) 63 | } 64 | return tpod, cleanupFuncs 65 | } 66 | 67 | func (volume *VolumeDetails) SetupDynamicPersistentVolumeClaim(client clientset.Interface, namespace *v1.Namespace, csiDriver driver.DynamicPVTestDriver) (*TestPersistentVolumeClaim, []func()) { 68 | cleanupFuncs := make([]func(), 0) 69 | By("setting up the StorageClass") 70 | storageClass := csiDriver.GetDynamicProvisionStorageClass(volume.Parameters, volume.MountOptions, volume.ReclaimPolicy, volume.VolumeBindingMode, []string{}, namespace.Name) 71 | tsc := NewTestStorageClass(client, namespace, storageClass) 72 | createdStorageClass := tsc.Create() 73 | cleanupFuncs = append(cleanupFuncs, tsc.Cleanup) 74 | By("setting up the PVC and PV") 75 | tpvc := NewTestPersistentVolumeClaim(client, namespace, volume.ClaimSize, &createdStorageClass) 76 | tpvc.Create() 77 | cleanupFuncs = append(cleanupFuncs, tpvc.Cleanup) 78 | // PV will not be ready until PVC is used in a pod when volumeBindingMode: WaitForFirstConsumer 79 | if volume.VolumeBindingMode == nil || *volume.VolumeBindingMode == storagev1.VolumeBindingImmediate { 80 | tpvc.WaitForBound() 81 | tpvc.ValidateProvisionedPersistentVolume() 82 | } 83 | 84 | return tpvc, cleanupFuncs 85 | } 86 | -------------------------------------------------------------------------------- /tests/e2e/cloud.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package e2e 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/aws/aws-sdk-go-v2/aws" 22 | "github.com/aws/aws-sdk-go-v2/config" 23 | "github.com/aws/aws-sdk-go-v2/service/ec2" 24 | ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" 25 | "github.com/aws/aws-sdk-go-v2/service/s3" 26 | "github.com/aws/aws-sdk-go-v2/service/s3/types" 27 | fsx "sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud" 28 | ) 29 | 30 | type cloud struct { 31 | ec2client *ec2.Client 32 | s3client *s3.Client 33 | fsx.Cloud 34 | } 35 | 36 | func NewCloud(region string) *cloud { 37 | config, _ := config.LoadDefaultConfig(context.Background(), config.WithRegion(region)) 38 | 39 | c, err := fsx.NewCloud(region) 40 | if err != nil { 41 | fmt.Sprintf("could not get NewCloud: %v", err) 42 | return nil 43 | } 44 | 45 | return &cloud{ 46 | ec2.NewFromConfig(config), 47 | s3.NewFromConfig(config), 48 | c, 49 | } 50 | } 51 | 52 | func (c *cloud) getNodeInstance(clusterName string) (*ec2types.Instance, error) { 53 | request := &ec2.DescribeInstancesInput{ 54 | Filters: []ec2types.Filter{ 55 | { 56 | Name: aws.String("tag:KubernetesCluster"), 57 | Values: []string{clusterName}, 58 | }, 59 | }, 60 | } 61 | 62 | instances := []ec2types.Instance{} 63 | ctx := context.Background() 64 | response, err := c.ec2client.DescribeInstances(ctx, request) 65 | if err != nil { 66 | return nil, err 67 | } 68 | for _, reservation := range response.Reservations { 69 | instances = append(instances, reservation.Instances...) 70 | } 71 | 72 | if len(instances) == 0 { 73 | return nil, fmt.Errorf("no instances in cluster %q found", clusterName) 74 | } 75 | 76 | return &instances[0], nil 77 | } 78 | 79 | func (c *cloud) createS3Bucket(name string, region string) error { 80 | request := &s3.CreateBucketInput{ 81 | Bucket: aws.String(name), 82 | CreateBucketConfiguration: &types.CreateBucketConfiguration{ 83 | LocationConstraint: types.BucketLocationConstraint(region), 84 | }, 85 | } 86 | 87 | ctx := context.Background() 88 | _, err := c.s3client.CreateBucket(ctx, request) 89 | if err != nil { 90 | return err 91 | } 92 | return nil 93 | } 94 | 95 | func (c *cloud) deleteS3Bucket(name string) error { 96 | request := &s3.DeleteBucketInput{ 97 | Bucket: aws.String(name), 98 | } 99 | 100 | ctx := context.Background() 101 | _, err := c.s3client.DeleteBucket(ctx, request) 102 | if err != nil { 103 | return err 104 | } 105 | return nil 106 | } 107 | 108 | func getSecurityGroupIds(node *ec2types.Instance) []string { 109 | groups := []string{} 110 | for _, sg := range node.SecurityGroups { 111 | groups = append(groups, *sg.GroupId) 112 | } 113 | return groups 114 | } 115 | -------------------------------------------------------------------------------- /pkg/cloud/mocks/mock_metadata.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud (interfaces: MetadataService) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -package=mocks -destination=./pkg/cloud/mocks/mock_metadata.go --build_flags=--mod=mod sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud MetadataService 7 | // 8 | 9 | // Package mocks is a generated GoMock package. 10 | package mocks 11 | 12 | import ( 13 | reflect "reflect" 14 | 15 | gomock "go.uber.org/mock/gomock" 16 | ) 17 | 18 | // MockMetadataService is a mock of MetadataService interface. 19 | type MockMetadataService struct { 20 | ctrl *gomock.Controller 21 | recorder *MockMetadataServiceMockRecorder 22 | isgomock struct{} 23 | } 24 | 25 | // MockMetadataServiceMockRecorder is the mock recorder for MockMetadataService. 26 | type MockMetadataServiceMockRecorder struct { 27 | mock *MockMetadataService 28 | } 29 | 30 | // NewMockMetadataService creates a new mock instance. 31 | func NewMockMetadataService(ctrl *gomock.Controller) *MockMetadataService { 32 | mock := &MockMetadataService{ctrl: ctrl} 33 | mock.recorder = &MockMetadataServiceMockRecorder{mock} 34 | return mock 35 | } 36 | 37 | // EXPECT returns an object that allows the caller to indicate expected use. 38 | func (m *MockMetadataService) EXPECT() *MockMetadataServiceMockRecorder { 39 | return m.recorder 40 | } 41 | 42 | // GetAvailabilityZone mocks base method. 43 | func (m *MockMetadataService) GetAvailabilityZone() string { 44 | m.ctrl.T.Helper() 45 | ret := m.ctrl.Call(m, "GetAvailabilityZone") 46 | ret0, _ := ret[0].(string) 47 | return ret0 48 | } 49 | 50 | // GetAvailabilityZone indicates an expected call of GetAvailabilityZone. 51 | func (mr *MockMetadataServiceMockRecorder) GetAvailabilityZone() *gomock.Call { 52 | mr.mock.ctrl.T.Helper() 53 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAvailabilityZone", reflect.TypeOf((*MockMetadataService)(nil).GetAvailabilityZone)) 54 | } 55 | 56 | // GetInstanceID mocks base method. 57 | func (m *MockMetadataService) GetInstanceID() string { 58 | m.ctrl.T.Helper() 59 | ret := m.ctrl.Call(m, "GetInstanceID") 60 | ret0, _ := ret[0].(string) 61 | return ret0 62 | } 63 | 64 | // GetInstanceID indicates an expected call of GetInstanceID. 65 | func (mr *MockMetadataServiceMockRecorder) GetInstanceID() *gomock.Call { 66 | mr.mock.ctrl.T.Helper() 67 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceID", reflect.TypeOf((*MockMetadataService)(nil).GetInstanceID)) 68 | } 69 | 70 | // GetInstanceType mocks base method. 71 | func (m *MockMetadataService) GetInstanceType() string { 72 | m.ctrl.T.Helper() 73 | ret := m.ctrl.Call(m, "GetInstanceType") 74 | ret0, _ := ret[0].(string) 75 | return ret0 76 | } 77 | 78 | // GetInstanceType indicates an expected call of GetInstanceType. 79 | func (mr *MockMetadataServiceMockRecorder) GetInstanceType() *gomock.Call { 80 | mr.mock.ctrl.T.Helper() 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceType", reflect.TypeOf((*MockMetadataService)(nil).GetInstanceType)) 82 | } 83 | 84 | // GetRegion mocks base method. 85 | func (m *MockMetadataService) GetRegion() string { 86 | m.ctrl.T.Helper() 87 | ret := m.ctrl.Call(m, "GetRegion") 88 | ret0, _ := ret[0].(string) 89 | return ret0 90 | } 91 | 92 | // GetRegion indicates an expected call of GetRegion. 93 | func (mr *MockMetadataServiceMockRecorder) GetRegion() *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRegion", reflect.TypeOf((*MockMetadataService)(nil).GetRegion)) 96 | } 97 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/controller-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/controller-serviceaccount.yaml 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: fsx-csi-controller-sa 7 | labels: 8 | app.kubernetes.io/name: aws-fsx-csi-driver 9 | --- 10 | # Source: aws-fsx-csi-driver/templates/controller-serviceaccount.yaml 11 | kind: ClusterRole 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | metadata: 14 | name: fsx-csi-external-provisioner-role 15 | labels: 16 | app.kubernetes.io/name: aws-fsx-csi-driver 17 | rules: 18 | - apiGroups: [""] 19 | resources: ["persistentvolumes"] 20 | verbs: ["get", "list", "watch", "create", "delete"] 21 | - apiGroups: [""] 22 | resources: ["persistentvolumeclaims"] 23 | verbs: ["get", "list", "watch", "update"] 24 | - apiGroups: ["storage.k8s.io"] 25 | resources: ["storageclasses"] 26 | verbs: ["get", "list", "watch"] 27 | - apiGroups: [""] 28 | resources: ["events"] 29 | verbs: ["list", "watch", "create", "update", "patch"] 30 | - apiGroups: ["storage.k8s.io"] 31 | resources: ["csinodes"] 32 | verbs: ["get", "list", "watch"] 33 | - apiGroups: [""] 34 | resources: ["nodes"] 35 | verbs: ["get", "list", "watch"] 36 | - apiGroups: ["coordination.k8s.io"] 37 | resources: ["leases"] 38 | verbs: ["get", "watch", "list", "delete", "update", "create"] 39 | --- 40 | # Source: aws-fsx-csi-driver/templates/controller-serviceaccount.yaml 41 | kind: ClusterRole 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | metadata: 44 | name: fsx-external-resizer-role 45 | labels: 46 | app.kubernetes.io/name: aws-fsx-csi-driver 47 | rules: 48 | # The following rule should be uncommented for plugins that require secrets 49 | # for provisioning. 50 | # - apiGroups: [""] 51 | # resources: ["secrets"] 52 | # verbs: ["get", "list", "watch"] 53 | - apiGroups: [ "" ] 54 | resources: [ "persistentvolumes" ] 55 | verbs: [ "get", "list", "watch", "update", "patch" ] 56 | - apiGroups: [ "" ] 57 | resources: [ "persistentvolumeclaims" ] 58 | verbs: [ "get", "list", "watch" ] 59 | - apiGroups: [ "" ] 60 | resources: [ "persistentvolumeclaims/status" ] 61 | verbs: [ "update", "patch" ] 62 | - apiGroups: [ "storage.k8s.io" ] 63 | resources: [ "storageclasses" ] 64 | verbs: [ "get", "list", "watch" ] 65 | - apiGroups: [ "" ] 66 | resources: [ "events" ] 67 | verbs: [ "list", "watch", "create", "update", "patch" ] 68 | - apiGroups: [ "" ] 69 | resources: [ "pods" ] 70 | verbs: [ "get", "list", "watch" ] 71 | --- 72 | # Source: aws-fsx-csi-driver/templates/controller-serviceaccount.yaml 73 | kind: ClusterRoleBinding 74 | apiVersion: rbac.authorization.k8s.io/v1 75 | metadata: 76 | name: fsx-csi-external-provisioner-binding 77 | labels: 78 | app.kubernetes.io/name: aws-fsx-csi-driver 79 | subjects: 80 | - kind: ServiceAccount 81 | name: fsx-csi-controller-sa 82 | roleRef: 83 | kind: ClusterRole 84 | name: fsx-csi-external-provisioner-role 85 | apiGroup: rbac.authorization.k8s.io 86 | --- 87 | # Source: aws-fsx-csi-driver/templates/controller-serviceaccount.yaml 88 | kind: ClusterRoleBinding 89 | apiVersion: rbac.authorization.k8s.io/v1 90 | metadata: 91 | name: fsx-csi-resizer-binding 92 | labels: 93 | app.kubernetes.io/name: aws-fsx-csi-driver 94 | subjects: 95 | - kind: ServiceAccount 96 | name: fsx-csi-controller-sa 97 | roleRef: 98 | kind: ClusterRole 99 | name: fsx-external-resizer-role 100 | apiGroup: rbac.authorization.k8s.io 101 | -------------------------------------------------------------------------------- /hack/e2e/eksctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | function eksctl_install() { 6 | INSTALL_PATH=${1} 7 | EKSCTL_VERSION=${2} 8 | if [[ ! -e ${INSTALL_PATH}/eksctl ]]; then 9 | EKSCTL_DOWNLOAD_URL="https://github.com/weaveworks/eksctl/releases/download/v${EKSCTL_VERSION}/eksctl_$(uname -s)_amd64.tar.gz" 10 | curl --silent --location "${EKSCTL_DOWNLOAD_URL}" | tar xz -C "${INSTALL_PATH}" 11 | chmod +x "${INSTALL_PATH}"/eksctl 12 | fi 13 | } 14 | 15 | function eksctl_create_cluster() { 16 | SSH_KEY_PATH=${1} 17 | CLUSTER_NAME=${2} 18 | BIN=${3} 19 | ZONES=${4} 20 | INSTANCE_TYPE=${5} 21 | K8S_VERSION=${6} 22 | CLUSTER_FILE=${7} 23 | KUBECONFIG=${8} 24 | EKSCTL_PATCH_FILE=${9} 25 | EKSCTL_ADMIN_ROLE=${10} 26 | 27 | generate_ssh_key "${SSH_KEY_PATH}" 28 | 29 | CLUSTER_NAME="${CLUSTER_NAME//./-}" 30 | 31 | if eksctl_cluster_exists "${CLUSTER_NAME}" "${BIN}"; then 32 | loudecho "Upgrading cluster $CLUSTER_NAME with $CLUSTER_FILE" 33 | ${BIN} upgrade cluster -f "${CLUSTER_FILE}" 34 | else 35 | loudecho "Creating cluster $CLUSTER_NAME with $CLUSTER_FILE (dry run)" 36 | ${BIN} create cluster \ 37 | --managed \ 38 | --ssh-access \ 39 | --ssh-public-key "${SSH_KEY_PATH}".pub \ 40 | --zones "${ZONES}" \ 41 | --nodes=3 \ 42 | --instance-types="${INSTANCE_TYPE}" \ 43 | --version="${K8S_VERSION}" \ 44 | --disable-pod-imds \ 45 | --dry-run \ 46 | "${CLUSTER_NAME}" > "${CLUSTER_FILE}" 47 | 48 | if test -f "$EKSCTL_PATCH_FILE"; then 49 | eksctl_patch_cluster_file "$CLUSTER_FILE" "$EKSCTL_PATCH_FILE" 50 | fi 51 | 52 | loudecho "Creating cluster $CLUSTER_NAME with $CLUSTER_FILE" 53 | ${BIN} create cluster -f "${CLUSTER_FILE}" --kubeconfig "${KUBECONFIG}" 54 | fi 55 | 56 | loudecho "Cluster ${CLUSTER_NAME} kubecfg written to ${KUBECONFIG}" 57 | 58 | loudecho "Getting cluster ${CLUSTER_NAME}" 59 | ${BIN} get cluster "${CLUSTER_NAME}" 60 | 61 | if [[ -n "$EKSCTL_ADMIN_ROLE" ]]; then 62 | AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) 63 | ADMIN_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${EKSCTL_ADMIN_ROLE}" 64 | loudecho "Granting ${ADMIN_ARN} admin access to the cluster" 65 | ${BIN} create iamidentitymapping --cluster "${CLUSTER_NAME}" --arn "${ADMIN_ARN}" --group system:masters --username admin 66 | fi 67 | 68 | return $? 69 | } 70 | 71 | function eksctl_cluster_exists() { 72 | CLUSTER_NAME=${1} 73 | BIN=${2} 74 | set +e 75 | if ${BIN} get cluster "${CLUSTER_NAME}"; then 76 | set -e 77 | return 0 78 | else 79 | set -e 80 | return 1 81 | fi 82 | } 83 | 84 | function eksctl_delete_cluster() { 85 | BIN=${1} 86 | CLUSTER_NAME=${2} 87 | loudecho "Deleting cluster ${CLUSTER_NAME}" 88 | ${BIN} delete cluster "${CLUSTER_NAME}" 89 | } 90 | 91 | function eksctl_patch_cluster_file() { 92 | CLUSTER_FILE=${1} # input must be yaml 93 | EKSCTL_PATCH_FILE=${2} # input must be yaml 94 | 95 | loudecho "Patching cluster $CLUSTER_NAME with $EKSCTL_PATCH_FILE" 96 | 97 | # Temporary intermediate files for patching 98 | CLUSTER_FILE_0=$CLUSTER_FILE.0 99 | CLUSTER_FILE_1=$CLUSTER_FILE.1 100 | 101 | cp "$CLUSTER_FILE" "$CLUSTER_FILE_0" 102 | 103 | # Patch only the Cluster 104 | kubectl patch -f "$CLUSTER_FILE_0" --local --type merge --patch "$(cat "$EKSCTL_PATCH_FILE")" -o yaml > "$CLUSTER_FILE_1" 105 | mv "$CLUSTER_FILE_1" "$CLUSTER_FILE_0" 106 | 107 | # Done patching, overwrite original CLUSTER_FILE 108 | mv "$CLUSTER_FILE_0" "$CLUSTER_FILE" # output is yaml 109 | } 110 | -------------------------------------------------------------------------------- /pkg/cloud/fakes.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloud 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "math/rand" 23 | "time" 24 | ) 25 | 26 | var random *rand.Rand 27 | 28 | func init() { 29 | random = rand.New(rand.NewSource(time.Now().UnixNano())) 30 | } 31 | 32 | type FakeCloudProvider struct { 33 | m *Metadata 34 | fileSystems map[string]*FileSystem 35 | } 36 | 37 | func NewFakeCloudProvider() *FakeCloudProvider { 38 | return &FakeCloudProvider{ 39 | m: &Metadata{InstanceID: "InstanceID", InstanceType: "Region", Region: "az"}, 40 | fileSystems: make(map[string]*FileSystem), 41 | } 42 | } 43 | 44 | func (c *FakeCloudProvider) GetMetadata() MetadataService { 45 | return c.m 46 | } 47 | 48 | func (c *FakeCloudProvider) CreateFileSystem(ctx context.Context, volumeName string, fileSystemOptions *FileSystemOptions) (fs *FileSystem, err error) { 49 | fs, exists := c.fileSystems[volumeName] 50 | if exists { 51 | if fs.CapacityGiB == fileSystemOptions.CapacityGiB { 52 | return fs, nil 53 | } else { 54 | return nil, ErrFsExistsDiffSize 55 | } 56 | } 57 | 58 | fs = &FileSystem{ 59 | FileSystemId: fmt.Sprintf("fs-%d", random.Uint64()), 60 | CapacityGiB: fileSystemOptions.CapacityGiB, 61 | DnsName: "test.us-east-1.fsx.amazonaws.com", 62 | MountName: "random", 63 | StorageType: fileSystemOptions.StorageType, 64 | DeploymentType: fileSystemOptions.DeploymentType, 65 | PerUnitStorageThroughput: fileSystemOptions.PerUnitStorageThroughput, 66 | } 67 | c.fileSystems[volumeName] = fs 68 | return fs, nil 69 | } 70 | 71 | func (c *FakeCloudProvider) ResizeFileSystem(ctx context.Context, volumeName string, newSizeGiB int32) (int32, error) { 72 | fs, exists := c.fileSystems[volumeName] 73 | if !exists { 74 | return 0, ErrNotFound 75 | } 76 | 77 | fs.CapacityGiB = newSizeGiB 78 | c.fileSystems[volumeName] = fs 79 | return newSizeGiB, nil 80 | } 81 | 82 | func (c *FakeCloudProvider) DeleteFileSystem(ctx context.Context, volumeID string) (err error) { 83 | delete(c.fileSystems, volumeID) 84 | for name, fs := range c.fileSystems { 85 | if fs.FileSystemId == volumeID { 86 | delete(c.fileSystems, name) 87 | } 88 | } 89 | return nil 90 | } 91 | 92 | func (c *FakeCloudProvider) DescribeFileSystem(ctx context.Context, volumeID string) (fs *FileSystem, err error) { 93 | for _, fs := range c.fileSystems { 94 | if fs.FileSystemId == volumeID { 95 | return fs, nil 96 | } 97 | } 98 | return nil, ErrNotFound 99 | } 100 | 101 | func (c *FakeCloudProvider) WaitForFileSystemAvailable(ctx context.Context, fileSystemId string) error { 102 | return nil 103 | } 104 | 105 | func (c *FakeCloudProvider) WaitForFileSystemResize(ctx context.Context, fileSystemId string, resizeGiB int32) error { 106 | return nil 107 | } 108 | 109 | func (c *FakeCloudProvider) FindFileSystemByVolumeName(ctx context.Context, volumeName string) (*FileSystem, error) { 110 | // Check if filesystem exists for this volume name 111 | fs, exists := c.fileSystems[volumeName] 112 | if exists { 113 | return fs, nil 114 | } 115 | return nil, ErrNotFound 116 | } 117 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/controller-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.controller.serviceAccount.create }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ .Values.controller.serviceAccount.name }} 6 | labels: 7 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 8 | {{- with .Values.controller.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- else }} 13 | {{- $exists := (lookup "v1" "ServiceAccount" .Release.Namespace .Values.controller.serviceAccount.name) }} 14 | {{- if not $exists }} 15 | {{- fail (printf "create serviceaccount %s/%s or set .controller.serviceaccount.create true" .Release.Namespace .Values.controller.serviceAccount.name) }} 16 | {{- end }} 17 | {{- end }} 18 | --- 19 | 20 | kind: ClusterRole 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | metadata: 23 | name: fsx-csi-external-provisioner-role 24 | labels: 25 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 26 | rules: 27 | - apiGroups: [""] 28 | resources: ["persistentvolumes"] 29 | verbs: ["get", "list", "watch", "create", "delete"] 30 | - apiGroups: [""] 31 | resources: ["persistentvolumeclaims"] 32 | verbs: ["get", "list", "watch", "update"] 33 | - apiGroups: ["storage.k8s.io"] 34 | resources: ["storageclasses"] 35 | verbs: ["get", "list", "watch"] 36 | - apiGroups: [""] 37 | resources: ["events"] 38 | verbs: ["list", "watch", "create", "update", "patch"] 39 | - apiGroups: ["storage.k8s.io"] 40 | resources: ["csinodes"] 41 | verbs: ["get", "list", "watch"] 42 | - apiGroups: [""] 43 | resources: ["nodes"] 44 | verbs: ["get", "list", "watch"] 45 | - apiGroups: ["coordination.k8s.io"] 46 | resources: ["leases"] 47 | verbs: ["get", "watch", "list", "delete", "update", "create"] 48 | --- 49 | 50 | kind: ClusterRoleBinding 51 | apiVersion: rbac.authorization.k8s.io/v1 52 | metadata: 53 | name: fsx-csi-external-provisioner-binding 54 | labels: 55 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 56 | subjects: 57 | - kind: ServiceAccount 58 | name: {{ .Values.controller.serviceAccount.name }} 59 | namespace: {{ .Release.Namespace }} 60 | roleRef: 61 | kind: ClusterRole 62 | name: fsx-csi-external-provisioner-role 63 | apiGroup: rbac.authorization.k8s.io 64 | --- 65 | 66 | kind: ClusterRole 67 | apiVersion: rbac.authorization.k8s.io/v1 68 | metadata: 69 | name: fsx-external-resizer-role 70 | labels: 71 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 72 | rules: 73 | # The following rule should be uncommented for plugins that require secrets 74 | # for provisioning. 75 | # - apiGroups: [""] 76 | # resources: ["secrets"] 77 | # verbs: ["get", "list", "watch"] 78 | - apiGroups: [ "" ] 79 | resources: [ "persistentvolumes" ] 80 | verbs: [ "get", "list", "watch", "update", "patch" ] 81 | - apiGroups: [ "" ] 82 | resources: [ "persistentvolumeclaims" ] 83 | verbs: [ "get", "list", "watch" ] 84 | - apiGroups: [ "" ] 85 | resources: [ "persistentvolumeclaims/status" ] 86 | verbs: [ "update", "patch" ] 87 | - apiGroups: [ "storage.k8s.io" ] 88 | resources: [ "storageclasses" ] 89 | verbs: [ "get", "list", "watch" ] 90 | - apiGroups: [ "" ] 91 | resources: [ "events" ] 92 | verbs: [ "list", "watch", "create", "update", "patch" ] 93 | - apiGroups: [ "" ] 94 | resources: [ "pods" ] 95 | verbs: [ "get", "list", "watch" ] 96 | --- 97 | 98 | kind: ClusterRoleBinding 99 | apiVersion: rbac.authorization.k8s.io/v1 100 | metadata: 101 | name: fsx-csi-resizer-binding 102 | labels: 103 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 104 | subjects: 105 | - kind: ServiceAccount 106 | name: {{ .Values.controller.serviceAccount.name }} 107 | namespace: {{ .Release.Namespace }} 108 | roleRef: 109 | kind: ClusterRole 110 | name: fsx-external-resizer-role 111 | apiGroup: rbac.authorization.k8s.io 112 | -------------------------------------------------------------------------------- /cmd/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | flag "github.com/spf13/pflag" 22 | "k8s.io/component-base/featuregate" 23 | logsapi "k8s.io/component-base/logs/api/v1" 24 | "k8s.io/klog/v2" 25 | "os" 26 | "sigs.k8s.io/aws-fsx-csi-driver/cmd/hooks" 27 | "sigs.k8s.io/aws-fsx-csi-driver/cmd/options" 28 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud" 29 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver" 30 | "strings" 31 | ) 32 | 33 | // Options is the combined set of options for all operating modes. 34 | type Options struct { 35 | *options.ServerOptions 36 | *options.ControllerOptions 37 | *options.NodeOptions 38 | } 39 | 40 | // used for testing 41 | var osExit = os.Exit 42 | 43 | var featureGate = featuregate.NewFeatureGate() 44 | 45 | func GetOptions(fs *flag.FlagSet) *Options { 46 | var ( 47 | version = fs.Bool("version", false, "Print the version and exit.") 48 | toStderr = fs.Bool("logtostderr", false, "log to standard error instead of files. DEPRECATED: will be removed in a future release.") 49 | 50 | args = os.Args[1:] 51 | 52 | serverOptions = options.ServerOptions{} 53 | controllerOptions = options.ControllerOptions{} 54 | nodeOptions = options.NodeOptions{} 55 | ) 56 | 57 | mode := serverOptions.AddFlags(fs) 58 | 59 | c := logsapi.NewLoggingConfiguration() 60 | 61 | err := logsapi.AddFeatureGates(featureGate) 62 | if err != nil { 63 | klog.ErrorS(err, "failed to add feature gates") 64 | } 65 | 66 | logsapi.AddFlags(c, fs) 67 | 68 | if len(os.Args) > 1 && !strings.HasPrefix(os.Args[1], "-") { 69 | cmd := os.Args[1] 70 | args = os.Args[2:] 71 | 72 | switch cmd { 73 | case "pre-stop-hook": 74 | clientset, clientErr := cloud.DefaultKubernetesAPIClient() 75 | if clientErr != nil { 76 | klog.ErrorS(err, "unable to communicate with k8s API") 77 | } else { 78 | err = hooks.PreStop(clientset) 79 | if err != nil { 80 | klog.ErrorS(err, "failed to execute PreStop lifecycle hook") 81 | klog.FlushAndExit(klog.ExitFlushTimeout, 1) 82 | } 83 | } 84 | klog.FlushAndExit(klog.ExitFlushTimeout, 0) 85 | 86 | default: 87 | klog.Errorf("Unknown driver command %s: Expected pre-stop-hook", cmd) 88 | klog.FlushAndExit(klog.ExitFlushTimeout, 0) 89 | } 90 | } 91 | 92 | switch { 93 | case mode == driver.ControllerMode: 94 | controllerOptions.AddFlags(fs) 95 | 96 | case mode == driver.NodeMode: 97 | nodeOptions.AddFlags(fs) 98 | 99 | case mode == driver.AllMode: 100 | controllerOptions.AddFlags(fs) 101 | nodeOptions.AddFlags(fs) 102 | default: 103 | fmt.Printf("Unknown mode: %s: expected %q, %q or %q", mode, driver.ControllerMode, driver.NodeMode, driver.AllMode) 104 | os.Exit(1) 105 | } 106 | 107 | if err := fs.Parse(args); err != nil { 108 | panic(err) 109 | } 110 | 111 | err = logsapi.ValidateAndApply(c, featureGate) 112 | if err != nil { 113 | klog.ErrorS(err, "failed to validate and apply logging configuration") 114 | } 115 | 116 | if *version { 117 | versionInfo, err := driver.GetVersionJSON() 118 | if err != nil { 119 | klog.ErrorS(err, "failed to get version") 120 | klog.FlushAndExit(klog.ExitFlushTimeout, 1) 121 | } 122 | fmt.Println(versionInfo) 123 | osExit(0) 124 | } 125 | 126 | if *toStderr { 127 | klog.SetOutput(os.Stderr) 128 | } 129 | 130 | return &Options{ 131 | ServerOptions: &serverOptions, 132 | ControllerOptions: &controllerOptions, 133 | NodeOptions: &nodeOptions, 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /pkg/driver/driver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package driver 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net" 23 | 24 | "github.com/container-storage-interface/spec/lib/go/csi" 25 | "google.golang.org/grpc" 26 | "k8s.io/klog/v2" 27 | "sigs.k8s.io/aws-fsx-csi-driver/pkg/util" 28 | ) 29 | 30 | // Mode is the operating mode of the CSI driver. 31 | type Mode string 32 | 33 | const ( 34 | // ControllerMode is the mode that only starts the controller service. 35 | ControllerMode = "controller" 36 | // NodeMode is the mode that only starts the node service. 37 | NodeMode = "node" 38 | // AllMode is the mode that only starts both the controller and the node service. 39 | AllMode = "all" 40 | ) 41 | 42 | const ( 43 | DriverName = "fsx.csi.aws.com" 44 | ) 45 | 46 | type Driver struct { 47 | controllerService 48 | nodeService 49 | 50 | srv *grpc.Server 51 | options *DriverOptions 52 | csi.UnimplementedIdentityServer 53 | } 54 | 55 | type DriverOptions struct { 56 | endpoint string 57 | mode string 58 | extraTags string 59 | } 60 | 61 | func NewDriver(options ...func(*DriverOptions)) (*Driver, error) { 62 | klog.InfoS("Driver Information", "Driver", DriverName, "Version", driverVersion) 63 | 64 | driverOptions := DriverOptions{ 65 | endpoint: DefaultCSIEndpoint, 66 | mode: AllMode, 67 | } 68 | for _, option := range options { 69 | option(&driverOptions) 70 | } 71 | 72 | driver := Driver{ 73 | options: &driverOptions, 74 | } 75 | 76 | switch driverOptions.mode { 77 | case ControllerMode: 78 | driver.controllerService = newControllerService(&driverOptions) 79 | case NodeMode: 80 | driver.nodeService = newNodeService(&driverOptions) 81 | case AllMode: 82 | driver.controllerService = newControllerService(&driverOptions) 83 | driver.nodeService = newNodeService(&driverOptions) 84 | default: 85 | return nil, fmt.Errorf("unknown mode: %s", driverOptions.mode) 86 | } 87 | 88 | return &driver, nil 89 | } 90 | 91 | func (d *Driver) Run() error { 92 | scheme, addr, err := util.ParseEndpoint(d.options.endpoint) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | listener, err := net.Listen(scheme, addr) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | logErr := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 103 | resp, err := handler(ctx, req) 104 | if err != nil { 105 | klog.ErrorS(err, "GRPC error") 106 | } 107 | return resp, err 108 | } 109 | opts := []grpc.ServerOption{ 110 | grpc.UnaryInterceptor(logErr), 111 | } 112 | d.srv = grpc.NewServer(opts...) 113 | 114 | csi.RegisterIdentityServer(d.srv, d) 115 | 116 | switch d.options.mode { 117 | case ControllerMode: 118 | csi.RegisterControllerServer(d.srv, d) 119 | case NodeMode: 120 | csi.RegisterNodeServer(d.srv, d) 121 | case AllMode: 122 | csi.RegisterControllerServer(d.srv, d) 123 | csi.RegisterNodeServer(d.srv, d) 124 | default: 125 | return fmt.Errorf("unknown mode: %s", d.options.mode) 126 | } 127 | 128 | klog.V(4).InfoS("Listening for connections", "address", listener.Addr()) 129 | return d.srv.Serve(listener) 130 | } 131 | 132 | func (d *Driver) Stop() { 133 | klog.InfoS("Stopping server") 134 | d.srv.Stop() 135 | } 136 | 137 | func WithEndpoint(endpoint string) func(*DriverOptions) { 138 | return func(o *DriverOptions) { 139 | o.endpoint = endpoint 140 | } 141 | } 142 | 143 | func WithMode(mode string) func(*DriverOptions) { 144 | return func(o *DriverOptions) { 145 | o.mode = mode 146 | } 147 | } 148 | 149 | func WithExtraTags(extraTags string) func(*DriverOptions) { 150 | return func(o *DriverOptions) { 151 | o.extraTags = extraTags 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/node-daemonset.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/node-daemonset.yaml 3 | apiVersion: apps/v1 4 | kind: DaemonSet 5 | metadata: 6 | name: fsx-csi-node 7 | labels: 8 | app.kubernetes.io/name: aws-fsx-csi-driver 9 | spec: 10 | selector: 11 | matchLabels: 12 | app: fsx-csi-node 13 | app.kubernetes.io/name: aws-fsx-csi-driver 14 | template: 15 | metadata: 16 | labels: 17 | app: fsx-csi-node 18 | app.kubernetes.io/name: aws-fsx-csi-driver 19 | spec: 20 | nodeSelector: 21 | kubernetes.io/os: linux 22 | dnsPolicy: ClusterFirst 23 | serviceAccountName: fsx-csi-node-sa 24 | terminationGracePeriodSeconds: 25 | priorityClassName: system-node-critical 26 | tolerations: 27 | - operator: Exists 28 | affinity: 29 | nodeAffinity: 30 | requiredDuringSchedulingIgnoredDuringExecution: 31 | nodeSelectorTerms: 32 | - matchExpressions: 33 | - key: eks.amazonaws.com/compute-type 34 | operator: NotIn 35 | values: 36 | - fargate 37 | containers: 38 | - name: fsx-plugin 39 | securityContext: 40 | privileged: true 41 | image: public.ecr.aws/fsx-csi-driver/aws-fsx-csi-driver:v1.8.0 42 | imagePullPolicy: IfNotPresent 43 | args: 44 | - --mode=node 45 | - --endpoint=$(CSI_ENDPOINT) 46 | - --logging-format=text 47 | - --v=2 48 | env: 49 | - name: CSI_ENDPOINT 50 | value: unix:/csi/csi.sock 51 | - name: CSI_NODE_NAME 52 | valueFrom: 53 | fieldRef: 54 | fieldPath: spec.nodeName 55 | volumeMounts: 56 | - name: kubelet-dir 57 | mountPath: /var/lib/kubelet 58 | mountPropagation: "Bidirectional" 59 | - name: plugin-dir 60 | mountPath: /csi 61 | ports: 62 | - name: healthz 63 | containerPort: 9810 64 | protocol: TCP 65 | livenessProbe: 66 | httpGet: 67 | path: /healthz 68 | port: healthz 69 | initialDelaySeconds: 10 70 | timeoutSeconds: 3 71 | periodSeconds: 2 72 | failureThreshold: 5 73 | lifecycle: 74 | preStop: 75 | exec: 76 | command: [ "/bin/aws-fsx-csi-driver", "pre-stop-hook" ] 77 | resources: 78 | limits: 79 | memory: 256Mi 80 | requests: 81 | cpu: 10m 82 | memory: 40Mi 83 | - name: node-driver-registrar 84 | image: public.ecr.aws/eks-distro/kubernetes-csi/node-driver-registrar:v2.13.0-eks-1-33-9 85 | imagePullPolicy: IfNotPresent 86 | args: 87 | - --csi-address=$(ADDRESS) 88 | - --kubelet-registration-path=$(DRIVER_REG_SOCK_PATH) 89 | - --v=2 90 | env: 91 | - name: ADDRESS 92 | value: /csi/csi.sock 93 | - name: DRIVER_REG_SOCK_PATH 94 | value: /var/lib/kubelet/plugins/fsx.csi.aws.com/csi.sock 95 | - name: KUBE_NODE_NAME 96 | valueFrom: 97 | fieldRef: 98 | fieldPath: spec.nodeName 99 | volumeMounts: 100 | - name: plugin-dir 101 | mountPath: /csi 102 | - name: registration-dir 103 | mountPath: /registration 104 | resources: 105 | limits: 106 | memory: 128Mi 107 | requests: 108 | cpu: 10m 109 | memory: 32Mi 110 | - name: liveness-probe 111 | image: public.ecr.aws/eks-distro/kubernetes-csi/livenessprobe:v2.15.0-eks-1-33-9 112 | imagePullPolicy: IfNotPresent 113 | args: 114 | - --csi-address=/csi/csi.sock 115 | - --health-port=9810 116 | volumeMounts: 117 | - mountPath: /csi 118 | name: plugin-dir 119 | resources: 120 | limits: 121 | memory: 128Mi 122 | requests: 123 | cpu: 10m 124 | memory: 32Mi 125 | volumes: 126 | - name: kubelet-dir 127 | hostPath: 128 | path: /var/lib/kubelet 129 | type: Directory 130 | - name: registration-dir 131 | hostPath: 132 | path: /var/lib/kubelet/plugins_registry/ 133 | type: Directory 134 | - name: plugin-dir 135 | hostPath: 136 | path: /var/lib/kubelet/plugins/fsx.csi.aws.com/ 137 | type: DirectoryOrCreate 138 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "fmt" 21 | "math" 22 | "net/url" 23 | "os" 24 | "path" 25 | "path/filepath" 26 | "reflect" 27 | "strings" 28 | 29 | "github.com/aws/aws-sdk-go-v2/service/fsx/types" 30 | ) 31 | 32 | const ( 33 | GiB = 1024 * 1024 * 1024 34 | ) 35 | 36 | // RoundUpVolumeSize rounds the volume size in bytes up to 37 | // 1200 GiB, 2400 GiB, or multiples of 3600 GiB for DeploymentType SCRATCH_1, 38 | // to 1200 GiB or multiples of 2400 GiB for DeploymentType SCRATCH_2 or for 39 | // DeploymentType PERSISTENT_1 and StorageType SSD, multiples of 6000 GiB for 40 | // DeploymentType PERSISTENT_1, StorageType HDD, and PerUnitStorageThroughput 12, 41 | // and multiples of 1800 GiB for DeploymentType PERSISTENT_1, StorageType HDD, and 42 | // PerUnitStorageThroughput 40. 43 | func RoundUpVolumeSize(volumeSizeBytes int64, deploymentType string, storageType string, perUnitStorageThroughput int32) int64 { 44 | if storageType == string(types.StorageTypeHdd) { 45 | if perUnitStorageThroughput == 12 { 46 | return calculateNumberOfAllocationUnits(volumeSizeBytes, 6000*GiB) * 6000 47 | } else { 48 | return calculateNumberOfAllocationUnits(volumeSizeBytes, 1800*GiB) * 1800 49 | } 50 | } else { 51 | if deploymentType == string(types.LustreDeploymentTypeScratch1) || 52 | deploymentType == "" { 53 | if volumeSizeBytes < 3600*GiB { 54 | return calculateNumberOfAllocationUnits(volumeSizeBytes, 1200*GiB) * 1200 55 | } else { 56 | return calculateNumberOfAllocationUnits(volumeSizeBytes, 3600*GiB) * 3600 57 | } 58 | } else { 59 | if volumeSizeBytes < 2400*GiB { 60 | return calculateNumberOfAllocationUnits(volumeSizeBytes, 1200*GiB) * 1200 61 | } else { 62 | return calculateNumberOfAllocationUnits(volumeSizeBytes, 2400*GiB) * 2400 63 | } 64 | } 65 | } 66 | } 67 | 68 | // GiBToBytes converts GiB to Bytes 69 | func GiBToBytes(volumeSizeGiB int32) int64 { 70 | return int64(volumeSizeGiB) * GiB 71 | } 72 | 73 | // ConvertToInt32 converts a volume size in int64 to int32 74 | // raising an error if the size would overflow 75 | func ConvertToInt32(volumeSize int64) (int32, error) { 76 | if volumeSize > math.MaxInt32 { 77 | return 0, fmt.Errorf("volume size %d would overflow int32", volumeSize) 78 | } 79 | return int32(volumeSize), nil 80 | } 81 | 82 | func ParseEndpoint(endpoint string) (string, string, error) { 83 | u, err := url.Parse(endpoint) 84 | if err != nil { 85 | return "", "", fmt.Errorf("could not parse endpoint: %v", err) 86 | } 87 | 88 | addr := path.Join(u.Host, filepath.FromSlash(u.Path)) 89 | 90 | scheme := strings.ToLower(u.Scheme) 91 | switch scheme { 92 | case "tcp": 93 | case "unix": 94 | addr = path.Join("/", addr) 95 | if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { 96 | return "", "", fmt.Errorf("could not remove unix domain socket %q: %v", addr, err) 97 | } 98 | default: 99 | return "", "", fmt.Errorf("unsupported protocol: %s", scheme) 100 | } 101 | 102 | return scheme, addr, nil 103 | } 104 | 105 | // calculateNumberOfAllocationUnits calculates the number of allocation units required to accommodate 106 | // the specified volume size, rounding up as necessary. 107 | // Both the volume size and the allocation unit size are in bytes 108 | func calculateNumberOfAllocationUnits(volumeSizeBytes int64, allocationUnitBytes int64) int64 { 109 | return (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes 110 | } 111 | 112 | // GetURLHost returns hostname of given url 113 | func GetURLHost(urlStr string) (string, error) { 114 | u, err := url.Parse(urlStr) 115 | 116 | if err != nil { 117 | return "", fmt.Errorf("could not parse url: %v", err) 118 | } 119 | 120 | return u.Host, nil 121 | } 122 | 123 | // SanitizeRequest takes a request object and returns a copy of the request with 124 | // the "Secrets" field cleared. 125 | func SanitizeRequest(req interface{}) interface{} { 126 | v := reflect.ValueOf(&req).Elem() 127 | e := reflect.New(v.Elem().Type()).Elem() 128 | 129 | e.Set(v.Elem()) 130 | 131 | f := reflect.Indirect(e).FieldByName("Secrets") 132 | 133 | if f.IsValid() && f.CanSet() && f.Kind() == reflect.Map { 134 | f.Set(reflect.MakeMap(f.Type())) 135 | v.Set(e) 136 | } 137 | return req 138 | } 139 | -------------------------------------------------------------------------------- /pkg/cloud/mocks/mock_fsx.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud (interfaces: FSx) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -package=mocks -destination=./pkg/cloud/mocks/mock_fsx.go --build_flags=--mod=mod sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud FSx 7 | // 8 | 9 | // Package mocks is a generated GoMock package. 10 | package mocks 11 | 12 | import ( 13 | context "context" 14 | reflect "reflect" 15 | 16 | fsx "github.com/aws/aws-sdk-go-v2/service/fsx" 17 | gomock "go.uber.org/mock/gomock" 18 | ) 19 | 20 | // MockFSx is a mock of FSx interface. 21 | type MockFSx struct { 22 | ctrl *gomock.Controller 23 | recorder *MockFSxMockRecorder 24 | isgomock struct{} 25 | } 26 | 27 | // MockFSxMockRecorder is the mock recorder for MockFSx. 28 | type MockFSxMockRecorder struct { 29 | mock *MockFSx 30 | } 31 | 32 | // NewMockFSx creates a new mock instance. 33 | func NewMockFSx(ctrl *gomock.Controller) *MockFSx { 34 | mock := &MockFSx{ctrl: ctrl} 35 | mock.recorder = &MockFSxMockRecorder{mock} 36 | return mock 37 | } 38 | 39 | // EXPECT returns an object that allows the caller to indicate expected use. 40 | func (m *MockFSx) EXPECT() *MockFSxMockRecorder { 41 | return m.recorder 42 | } 43 | 44 | // CreateFileSystem mocks base method. 45 | func (m *MockFSx) CreateFileSystem(arg0 context.Context, arg1 *fsx.CreateFileSystemInput, arg2 ...func(*fsx.Options)) (*fsx.CreateFileSystemOutput, error) { 46 | m.ctrl.T.Helper() 47 | varargs := []any{arg0, arg1} 48 | for _, a := range arg2 { 49 | varargs = append(varargs, a) 50 | } 51 | ret := m.ctrl.Call(m, "CreateFileSystem", varargs...) 52 | ret0, _ := ret[0].(*fsx.CreateFileSystemOutput) 53 | ret1, _ := ret[1].(error) 54 | return ret0, ret1 55 | } 56 | 57 | // CreateFileSystem indicates an expected call of CreateFileSystem. 58 | func (mr *MockFSxMockRecorder) CreateFileSystem(arg0, arg1 any, arg2 ...any) *gomock.Call { 59 | mr.mock.ctrl.T.Helper() 60 | varargs := append([]any{arg0, arg1}, arg2...) 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileSystem", reflect.TypeOf((*MockFSx)(nil).CreateFileSystem), varargs...) 62 | } 63 | 64 | // DeleteFileSystem mocks base method. 65 | func (m *MockFSx) DeleteFileSystem(arg0 context.Context, arg1 *fsx.DeleteFileSystemInput, arg2 ...func(*fsx.Options)) (*fsx.DeleteFileSystemOutput, error) { 66 | m.ctrl.T.Helper() 67 | varargs := []any{arg0, arg1} 68 | for _, a := range arg2 { 69 | varargs = append(varargs, a) 70 | } 71 | ret := m.ctrl.Call(m, "DeleteFileSystem", varargs...) 72 | ret0, _ := ret[0].(*fsx.DeleteFileSystemOutput) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // DeleteFileSystem indicates an expected call of DeleteFileSystem. 78 | func (mr *MockFSxMockRecorder) DeleteFileSystem(arg0, arg1 any, arg2 ...any) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | varargs := append([]any{arg0, arg1}, arg2...) 81 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFileSystem", reflect.TypeOf((*MockFSx)(nil).DeleteFileSystem), varargs...) 82 | } 83 | 84 | // DescribeFileSystems mocks base method. 85 | func (m *MockFSx) DescribeFileSystems(arg0 context.Context, arg1 *fsx.DescribeFileSystemsInput, arg2 ...func(*fsx.Options)) (*fsx.DescribeFileSystemsOutput, error) { 86 | m.ctrl.T.Helper() 87 | varargs := []any{arg0, arg1} 88 | for _, a := range arg2 { 89 | varargs = append(varargs, a) 90 | } 91 | ret := m.ctrl.Call(m, "DescribeFileSystems", varargs...) 92 | ret0, _ := ret[0].(*fsx.DescribeFileSystemsOutput) 93 | ret1, _ := ret[1].(error) 94 | return ret0, ret1 95 | } 96 | 97 | // DescribeFileSystems indicates an expected call of DescribeFileSystems. 98 | func (mr *MockFSxMockRecorder) DescribeFileSystems(arg0, arg1 any, arg2 ...any) *gomock.Call { 99 | mr.mock.ctrl.T.Helper() 100 | varargs := append([]any{arg0, arg1}, arg2...) 101 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeFileSystems", reflect.TypeOf((*MockFSx)(nil).DescribeFileSystems), varargs...) 102 | } 103 | 104 | // UpdateFileSystem mocks base method. 105 | func (m *MockFSx) UpdateFileSystem(arg0 context.Context, arg1 *fsx.UpdateFileSystemInput, arg2 ...func(*fsx.Options)) (*fsx.UpdateFileSystemOutput, error) { 106 | m.ctrl.T.Helper() 107 | varargs := []any{arg0, arg1} 108 | for _, a := range arg2 { 109 | varargs = append(varargs, a) 110 | } 111 | ret := m.ctrl.Call(m, "UpdateFileSystem", varargs...) 112 | ret0, _ := ret[0].(*fsx.UpdateFileSystemOutput) 113 | ret1, _ := ret[1].(error) 114 | return ret0, ret1 115 | } 116 | 117 | // UpdateFileSystem indicates an expected call of UpdateFileSystem. 118 | func (mr *MockFSxMockRecorder) UpdateFileSystem(arg0, arg1 any, arg2 ...any) *gomock.Call { 119 | mr.mock.ctrl.T.Helper() 120 | varargs := append([]any{arg0, arg1}, arg2...) 121 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFileSystem", reflect.TypeOf((*MockFSx)(nil).UpdateFileSystem), varargs...) 122 | } 123 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/controller-deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: aws-fsx-csi-driver/templates/controller-deployment.yaml 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: fsx-csi-controller 7 | labels: 8 | app.kubernetes.io/name: aws-fsx-csi-driver 9 | spec: 10 | replicas: 2 11 | selector: 12 | matchLabels: 13 | app: fsx-csi-controller 14 | app.kubernetes.io/name: aws-fsx-csi-driver 15 | template: 16 | metadata: 17 | labels: 18 | app: fsx-csi-controller 19 | app.kubernetes.io/name: aws-fsx-csi-driver 20 | spec: 21 | nodeSelector: 22 | kubernetes.io/os: linux 23 | serviceAccountName: fsx-csi-controller-sa 24 | priorityClassName: system-cluster-critical 25 | tolerations: 26 | - key: CriticalAddonsOnly 27 | operator: Exists 28 | - effect: NoExecute 29 | operator: Exists 30 | tolerationSeconds: 300 31 | containers: 32 | - name: fsx-plugin 33 | image: public.ecr.aws/fsx-csi-driver/aws-fsx-csi-driver:v1.8.0 34 | imagePullPolicy: IfNotPresent 35 | args: 36 | - --mode=controller 37 | - --endpoint=$(CSI_ENDPOINT) 38 | - --logging-format=text 39 | - --v=2 40 | env: 41 | - name: CSI_ENDPOINT 42 | value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock 43 | - name: CSI_NODE_NAME 44 | valueFrom: 45 | fieldRef: 46 | fieldPath: spec.nodeName 47 | - name: AWS_ACCESS_KEY_ID 48 | valueFrom: 49 | secretKeyRef: 50 | name: aws-secret 51 | key: key_id 52 | optional: true 53 | - name: AWS_SECRET_ACCESS_KEY 54 | valueFrom: 55 | secretKeyRef: 56 | name: aws-secret 57 | key: access_key 58 | optional: true 59 | volumeMounts: 60 | - name: socket-dir 61 | mountPath: /var/lib/csi/sockets/pluginproxy/ 62 | ports: 63 | - name: healthz 64 | containerPort: 9910 65 | protocol: TCP 66 | livenessProbe: 67 | httpGet: 68 | path: /healthz 69 | port: healthz 70 | initialDelaySeconds: 10 71 | timeoutSeconds: 3 72 | periodSeconds: 2 73 | failureThreshold: 5 74 | resources: 75 | limits: 76 | memory: 256Mi 77 | requests: 78 | cpu: 10m 79 | memory: 40Mi 80 | - name: csi-provisioner 81 | image: public.ecr.aws/eks-distro/kubernetes-csi/external-provisioner:v5.2.0-eks-1-33-9 82 | args: 83 | - --csi-address=$(ADDRESS) 84 | - --v=2 85 | - --timeout=5m 86 | - --extra-create-metadata 87 | - --leader-election=true 88 | env: 89 | - name: ADDRESS 90 | value: /var/lib/csi/sockets/pluginproxy/csi.sock 91 | volumeMounts: 92 | - name: socket-dir 93 | mountPath: /var/lib/csi/sockets/pluginproxy/ 94 | resources: 95 | limits: 96 | memory: 128Mi 97 | requests: 98 | cpu: 10m 99 | memory: 32Mi 100 | - name: csi-resizer 101 | image: public.ecr.aws/eks-distro/kubernetes-csi/external-resizer:v1.13.2-eks-1-33-9 102 | args: 103 | - --csi-address=$(ADDRESS) 104 | - --v=2 105 | - --leader-election=true 106 | - --timeout=5m 107 | env: 108 | - name: ADDRESS 109 | value: /var/lib/csi/sockets/pluginproxy/csi.sock 110 | volumeMounts: 111 | - name: socket-dir 112 | mountPath: /var/lib/csi/sockets/pluginproxy/ 113 | resources: 114 | limits: 115 | memory: 128Mi 116 | requests: 117 | cpu: 10m 118 | memory: 32Mi 119 | - name: liveness-probe 120 | image: public.ecr.aws/eks-distro/kubernetes-csi/livenessprobe:v2.15.0-eks-1-33-9 121 | args: 122 | - --csi-address=/csi/csi.sock 123 | - --health-port=9910 124 | volumeMounts: 125 | - name: socket-dir 126 | mountPath: /csi 127 | resources: 128 | limits: 129 | memory: 128Mi 130 | requests: 131 | cpu: 10m 132 | memory: 32Mi 133 | volumes: 134 | - name: socket-dir 135 | emptyDir: {} 136 | affinity: 137 | nodeAffinity: 138 | preferredDuringSchedulingIgnoredDuringExecution: 139 | - preference: 140 | matchExpressions: 141 | - key: eks.amazonaws.com/compute-type 142 | operator: NotIn 143 | values: 144 | - fargate 145 | weight: 1 146 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sigs.k8s.io/aws-fsx-csi-driver 2 | 3 | require ( 4 | github.com/aws/aws-sdk-go-v2 v1.40.1 5 | github.com/aws/aws-sdk-go-v2/config v1.32.3 6 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 7 | github.com/aws/aws-sdk-go-v2/service/fsx v1.65.0 8 | github.com/container-storage-interface/spec v1.12.0 9 | github.com/kubernetes-csi/csi-test v2.0.1+incompatible 10 | github.com/onsi/ginkgo v1.16.5 11 | github.com/onsi/gomega v1.35.1 12 | github.com/spf13/pflag v1.0.10 13 | github.com/stretchr/testify v1.11.1 14 | go.uber.org/mock v0.6.0 15 | google.golang.org/grpc v1.77.0 16 | k8s.io/api v0.34.2 17 | k8s.io/apimachinery v0.34.2 18 | k8s.io/client-go v0.34.2 19 | k8s.io/component-base v0.34.2 20 | k8s.io/klog/v2 v2.130.1 21 | k8s.io/mount-utils v0.34.2 22 | 23 | ) 24 | 25 | require ( 26 | github.com/aws/aws-sdk-go-v2/credentials v1.19.3 // indirect 27 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect 28 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // indirect 29 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect 30 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect 31 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect 32 | github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect 33 | github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect 34 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect 35 | github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect 36 | github.com/aws/smithy-go v1.24.0 // indirect 37 | github.com/beorn7/perks v1.0.1 // indirect 38 | github.com/blang/semver/v4 v4.0.0 // indirect 39 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 40 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 41 | github.com/emicklei/go-restful/v3 v3.13.0 // indirect 42 | github.com/fsnotify/fsnotify v1.4.9 // indirect 43 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 44 | github.com/go-logr/logr v1.4.3 // indirect 45 | github.com/go-logr/zapr v1.3.0 // indirect 46 | github.com/go-openapi/jsonpointer v0.22.3 // indirect 47 | github.com/go-openapi/jsonreference v0.21.3 // indirect 48 | github.com/go-openapi/swag v0.25.4 // indirect 49 | github.com/go-openapi/swag/cmdutils v0.25.4 // indirect 50 | github.com/go-openapi/swag/conv v0.25.4 // indirect 51 | github.com/go-openapi/swag/fileutils v0.25.4 // indirect 52 | github.com/go-openapi/swag/jsonname v0.25.4 // indirect 53 | github.com/go-openapi/swag/jsonutils v0.25.4 // indirect 54 | github.com/go-openapi/swag/loading v0.25.4 // indirect 55 | github.com/go-openapi/swag/mangling v0.25.4 // indirect 56 | github.com/go-openapi/swag/netutils v0.25.4 // indirect 57 | github.com/go-openapi/swag/stringutils v0.25.4 // indirect 58 | github.com/go-openapi/swag/typeutils v0.25.4 // indirect 59 | github.com/go-openapi/swag/yamlutils v0.25.4 // indirect 60 | github.com/gogo/protobuf v1.3.2 // indirect 61 | github.com/google/gnostic-models v0.7.1 // indirect 62 | github.com/google/go-cmp v0.7.0 // indirect 63 | github.com/google/uuid v1.6.0 // indirect 64 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 65 | github.com/json-iterator/go v1.1.12 // indirect 66 | github.com/moby/sys/mountinfo v0.7.2 // indirect 67 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 68 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 69 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 70 | github.com/nxadm/tail v1.4.8 // indirect 71 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 72 | github.com/prometheus/client_golang v1.23.2 // indirect 73 | github.com/prometheus/client_model v0.6.2 // indirect 74 | github.com/prometheus/common v0.67.4 // indirect 75 | github.com/prometheus/procfs v0.19.2 // indirect 76 | github.com/spf13/cobra v1.10.1 // indirect 77 | github.com/x448/float16 v0.8.4 // indirect 78 | go.opentelemetry.io/otel v1.38.0 // indirect 79 | go.opentelemetry.io/otel/trace v1.38.0 // indirect 80 | go.uber.org/multierr v1.11.0 // indirect 81 | go.uber.org/zap v1.27.1 // indirect 82 | go.yaml.in/yaml/v2 v2.4.3 // indirect 83 | go.yaml.in/yaml/v3 v3.0.4 // indirect 84 | golang.org/x/net v0.47.0 // indirect 85 | golang.org/x/oauth2 v0.33.0 // indirect 86 | golang.org/x/sys v0.38.0 // indirect 87 | golang.org/x/term v0.37.0 // indirect 88 | golang.org/x/text v0.31.0 // indirect 89 | golang.org/x/time v0.14.0 // indirect 90 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect 91 | google.golang.org/protobuf v1.36.10 // indirect 92 | gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect 93 | gopkg.in/inf.v0 v0.9.1 // indirect 94 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 95 | gopkg.in/yaml.v2 v2.4.0 // indirect 96 | gopkg.in/yaml.v3 v3.0.1 // indirect 97 | k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect 98 | k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect 99 | sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect 100 | sigs.k8s.io/randfill v1.0.0 // indirect 101 | sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect 102 | sigs.k8s.io/yaml v1.6.0 // indirect 103 | ) 104 | 105 | go 1.24.2 106 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning_s3/README.md: -------------------------------------------------------------------------------- 1 | ## Dynamic Provisioning with Data Repository 2 | This example shows how to create a FSx for Lustre filesystem using persistence volume claim (PVC) with data repository integration. 3 | 4 | Amazon FSx for Lustre is deeply integrated with Amazon S3. This integration means that you can seamlessly access the objects stored in your Amazon S3 buckets from applications mounting your Amazon FSx for Lustre file system. Please check [Using Data Repositories](https://docs.aws.amazon.com/fsx/latest/LustreGuide/fsx-data-repositories.html) for details. 5 | 6 | ### Edit [StorageClass](./specs/storageclass.yaml) 7 | ``` 8 | kind: StorageClass 9 | apiVersion: storage.k8s.io/v1 10 | metadata: 11 | name: fsx-sc 12 | provisioner: fsx.csi.aws.com 13 | parameters: 14 | subnetId: subnet-056da83524edbe641 15 | securityGroupIds: sg-086f61ea73388fb6b 16 | autoImportPolicy: NONE 17 | s3ImportPath: s3://ml-training-data-000 18 | s3ExportPath: s3://ml-training-data-000/export 19 | deploymentType: SCRATCH_2 20 | ``` 21 | * subnetId - the subnet ID that the FSx for Lustre filesystem should be created inside. 22 | * securityGroupIds - a common separated list of security group IDs that should be attached to the filesystem. 23 | * autoImportPolicy - the policy FSx will follow that determines how the filesystem is automatically updated with changes made in the linked data repository. For a list of acceptable policies, please view the official FSx for Lustre documentation: https://docs.aws.amazon.com/fsx/latest/APIReference/API_CreateFileSystemLustreConfiguration.html 24 | * s3ImportPath(Optional) - S3 data repository you want to copy from S3 to persistent volume. 25 | * s3ExportPath(Optional) - S3 data repository you want to export new or modified files from persistent volume to S3. 26 | * deploymentType (Optional) - FSx for Lustre supports four deployment types, SCRATCH_1, SCRATCH_2, PERSISTENT_1 and PERSISTENT_2. Default: SCRATCH_1. 27 | * kmsKeyId (Optional) - for deployment types PERSISTENT_1 and PERSISTENT_2, customer can specify a KMS key to use. 28 | * perUnitStorageThroughput (Optional) - for deployment type PERSISTENT_1 and PERSISTENT_2, customer can specify the storage throughput. Default: "200". Note that customer has to specify as a string here like "200" or "100" etc. 29 | * storageType (Optional) - for deployment type PERSISTENT_1, customer can specify the storage type, either SSD or HDD. Default: "SSD" 30 | * driveCacheType (Required if storageType is "HDD") - for HDD PERSISTENT_1, specify the type of drive cache, either NONE or READ. 31 | * dataCompressionType (Optional) - FSx for Lustre supports data compression via LZ4 algorithm. Compression is disabled when the value is set to NONE. The default value is NONE 32 | 33 | Note: 34 | - S3 Bucket in s3ImportPath and s3ExportPath must be same, otherwise the driver can not create FSx for lustre successfully. 35 | - s3ImportPath can stand alone and a random path will be created automatically like `s3://ml-training-data-000/FSxLustre20190308T012310Z`. 36 | - s3ExportPath can not be given without specifying S3ImportPath. 37 | - autoImportPolicy can not be given without specifying S3ImportPath. 38 | 39 | ### Edit [Persistent Volume Claim Spec](./specs/claim.yaml) 40 | ``` 41 | apiVersion: v1 42 | kind: PersistentVolumeClaim 43 | metadata: 44 | name: fsx-claim 45 | spec: 46 | accessModes: 47 | - ReadWriteMany 48 | storageClassName: fsx-sc 49 | resources: 50 | requests: 51 | storage: 1200Gi 52 | ``` 53 | Update `spec.resource.requests.storage` with the storage capacity to request. The storage capacity value will be rounded up to 1200 GiB, 2400 GiB, or a multiple of 3600 GiB for SSD. If the storageType is specified as HDD, the storage capacity will be rounded up to 6000 GiB or a multiple of 6000 GiB if the perUnitStorageThroughput is 12, or rounded up to 1800 or a multiple of 1800 if the perUnitStorageThroughput is 40. 54 | 55 | ### Deploy the Application 56 | Create PVC, storageclass and the pod that consumes the PV: 57 | ```sh 58 | >> kubectl apply -f examples/kubernetes/dynamic_provisioning_s3/specs/storageclass.yaml 59 | >> kubectl apply -f examples/kubernetes/dynamic_provisioning_s3/specs/claim.yaml 60 | >> kubectl apply -f examples/kubernetes/dynamic_provisioning_s3/specs/pod.yaml 61 | ``` 62 | 63 | ### Use Case 1: Acccess S3 files from Lustre filesystem 64 | If you only want to import data and read it without any modification and creation. You can skip `s3ExportPath` parameter in your `storageclass.yaml` configuration. 65 | 66 | You can see S3 files are visible in the persistent volume. 67 | 68 | ``` 69 | >> kubectl exec -it fsx-app ls /data 70 | ``` 71 | 72 | ### Use case 2: Archive files to s3ExportPath 73 | For new files and modified files, you can use lustre user space tool to archive the data back to s3 on `s3ExportPath`. 74 | 75 | Pod `fsx-app` create a file `out.txt` in mounted volume, run following command to check this file: 76 | 77 | ```sh 78 | >> kubectl exec -ti fsx-app -- tail -f /data/out.txt 79 | ``` 80 | 81 | Export the file back to S3 using: 82 | ```sh 83 | >> kubectl exec -ti fsx-app -- lfs hsm_archive /data/out.txt 84 | ``` 85 | 86 | ## Notes 87 | * New created files won't be synced back to S3 automatically. In order to sync files to `s3ExportPath`, you need to install lustre client in your container image and manually run following command to force sync up using `lfs hsm_archive`. And the container should run in priviledged mode with `CAP_SYS_ADMIN` capability. 88 | * This example uses lifecycle hook to install lustre client for demostration purpose, a normal approach will be building a container image with lustre client. 89 | -------------------------------------------------------------------------------- /examples/kubernetes/dynamic_provisioning/README.md: -------------------------------------------------------------------------------- 1 | ## Dynamic Provisioning Example 2 | This example shows how to create a FSx for Lustre filesystem using persistence volume claim (PVC) and consumes it from a pod. 3 | 4 | 5 | ### Edit [StorageClass](./specs/storageclass.yaml) 6 | ``` 7 | kind: StorageClass 8 | apiVersion: storage.k8s.io/v1 9 | metadata: 10 | name: fsx-sc 11 | provisioner: fsx.csi.aws.com 12 | parameters: 13 | subnetId: subnet-056da83524edbe641 14 | securityGroupIds: sg-086f61ea73388fb6b 15 | deploymentType: PERSISTENT_1 16 | storageType: HDD 17 | ``` 18 | * subnetId - the subnet ID that the FSx for Lustre filesystem should be created inside. 19 | * securityGroupIds - a comma separated list of security group IDs that should be attached to the filesystem 20 | * deploymentType (Optional) - FSx for Lustre supports four deployment types, SCRATCH_1, SCRATCH_2, PERSISTENT_1 and PERSISTENT_2. Default: SCRATCH_1. 21 | * kmsKeyId (Optional) - for deployment types PERSISTENT_1 and PERSISTENT_2, customer can specify a KMS key to use. 22 | * perUnitStorageThroughput (Optional) - for deployment type PERSISTENT_1 and PERSISTENT_2, customer can specify the storage throughput. Default: "200". Note that customer has to specify as a string here like "200" or "100" etc. 23 | * storageType (Optional) - for deployment type PERSISTENT_1, customer can specify the storage type, either SSD or HDD. Default: "SSD" 24 | * driveCacheType (Required if storageType is "HDD") - for HDD PERSISTENT_1, specify the type of drive cache, either NONE or READ. 25 | * efaEnabled (Optional) - A boolean flag indicating whether Elastic Fabric Adapter (EFA) support is enabled for a PERSISTENT_2 file system with metadata configuration. 26 | * metadataConfigurationMode (Optional) - The metadata configuration mode for provisioning Metadata IOPS for a PERSISTENT_2 file system, either "AUTOMATIC" or "USER_PROVISIONED". 27 | * metadataIops (Required if metadataConfigurationMode is USER_PROVISIONED) - Specifies the number of Metadata IOPS to provision for the file system. 28 | * automaticBackupRetentionDays (Optional) - The number of days to retain automatic backups. The default is to retain backups for 7 days. Setting this value to 0 disables the creation of automatic backups. The maximum retention period for backups is 35 days 29 | * dailyAutomaticBackupStartTime (Optional) - The preferred time to take daily automatic backups, formatted HH:MM in the UTC time zone. 30 | * copyTagsToBackups (Optional) - A boolean flag indicating whether tags for the file system should be copied to backups. This value defaults to false. If it's set to true, all tags for the file system are copied to all automatic and user-initiated backups where the user doesn't specify tags. If this value is true, and you specify one or more tags, only the specified tags are copied to backups. If you specify one or more tags when creating a user-initiated backup, no tags are copied from the file system, regardless of this value. 31 | * dataCompressionType (Optional) - FSx for Lustre supports data compression via LZ4 algorithm. Compression is disabled when the value is set to NONE. The default value is NONE 32 | * weeklyMaintenanceStartTime (Optional) - The preferred start time to perform weekly maintenance, formatted d:HH:MM in the UTC time zone, where d is the weekday number, from 1 through 7, beginning with Monday and ending with Sunday. The default value is "7:09:00" (Sunday 09:00 UTC) 33 | * fileSystemTypeVersion (Optional) - Sets the Lustre version of the Amazon FSx for Lustre file system to be created. Valid values are 2.10 and 2.12. The default value is "2.10" 34 | * extraTags (Optional) - Tags that will be set on the FSx resource created in AWS, in the form of a comma separated list with each tag delimited by an equals sign (example - "Tag1=Value1,Tag2=Value2") . Default is a single tag with CSIVolumeName as the key and the generated volume name as it's value. 35 | 36 | ### Edit [Persistent Volume Claim Spec](./specs/claim.yaml) 37 | ``` 38 | apiVersion: v1 39 | kind: PersistentVolumeClaim 40 | metadata: 41 | name: fsx-claim 42 | spec: 43 | accessModes: 44 | - ReadWriteMany 45 | storageClassName: fsx-sc 46 | resources: 47 | requests: 48 | storage: 6000Gi 49 | ``` 50 | Update `spec.resource.requests.storage` with the storage capacity to request. The storage capacity value will be rounded up to 1200 GiB, 2400 GiB, or a multiple of 3600 GiB for SSD. If the storageType is specified as HDD, the storage capacity will be rounded up to 6000 GiB or a multiple of 6000 GiB if the perUnitStorageThroughput is 12, or rounded up to 1800 or a multiple of 1800 if the perUnitStorageThroughput is 40. 51 | 52 | ### Deploy the Application 53 | Create PVC, storageclass and the pod that consumes the PV: 54 | ```sh 55 | >> kubectl apply -f examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml 56 | >> kubectl apply -f examples/kubernetes/dynamic_provisioning/specs/claim.yaml 57 | >> kubectl apply -f examples/kubernetes/dynamic_provisioning/specs/pod.yaml 58 | ``` 59 | 60 | ### Check the Application uses FSx for Lustre filesystem 61 | After the objects are created, verify that pod is running: 62 | 63 | ```sh 64 | >> kubectl get pods 65 | ``` 66 | 67 | Also verify that data is written onto FSx for Luster filesystem: 68 | 69 | ```sh 70 | >> kubectl exec -ti fsx-app -- tail -f /data/out.txt 71 | ``` 72 | 73 | ### Notes for EFA enabled filesystems 74 | * See [EKS userguide](https://docs.aws.amazon.com/eks/latest/userguide/node-efa.html) for creating an EFA supported cluster 75 | * To configure EFA interfaces on EKS client nodes, consider adding the setup from [Configuring EFA clients](https://docs.aws.amazon.com/fsx/latest/LustreGuide/configure-efa-clients.html) to the `preBootstrapCommands` property of the nodegroup -------------------------------------------------------------------------------- /hack/release: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import argparse 18 | import hashlib 19 | import json 20 | import os 21 | import requests 22 | 23 | 24 | def file_sha512(fileName, repoName): 25 | download(fileName, repoName) 26 | with open(fileName, "rb") as file: 27 | m = hashlib.sha512() 28 | blob = file.read() 29 | m.update(blob) 30 | print( 31 | "[{}](https://github.com/{}/archive/{}) | `{}`".format( 32 | fileName, repoName, fileName, m.hexdigest() 33 | ) 34 | ) 35 | os.remove(fileName) 36 | 37 | 38 | def download(fileName, repoName): 39 | url = "https://github.com/{}/archive/{}".format(repoName, fileName) 40 | r = requests.get(url, allow_redirects=True) 41 | open(fileName, "wb").write(r.content) 42 | 43 | 44 | def print_header(repo, version): 45 | # Title 46 | print("# {}".format(version)) 47 | 48 | # documentation section 49 | print("[Documentation](https://github.com/{}/blob/{}/docs/README.md)\n".format(repo, version)) 50 | 51 | # sha512 52 | print("filename | sha512 hash") 53 | print("--------- | ------------") 54 | file_sha512(version + ".zip", repo) 55 | file_sha512(version + ".tar.gz", repo) 56 | 57 | 58 | class Github: 59 | def __init__(self, user, token): 60 | self._url = "https://api.github.com" 61 | self._user = user 62 | self._token = token 63 | 64 | def get_commits(self, repo, since): 65 | resp = requests.get( 66 | "{}/repos/{}/compare/{}...master".format(self._url, repo, since), 67 | auth=(self._user, self._token), 68 | ) 69 | jsonResp = json.loads(resp.content) 70 | return jsonResp["commits"] 71 | 72 | def to_pr_numbers(self, repo, commit): 73 | sha = commit["sha"] 74 | resp = requests.get( 75 | "{}/repos/{}/commits/{}/pulls".format(self._url, repo, sha), 76 | headers={"Accept": "application/vnd.github.groot-preview+json"}, 77 | auth=(self._user, self._token), 78 | ) 79 | jsonResp = json.loads(resp.content) 80 | ret = [] 81 | for pr in jsonResp: 82 | ret.append(pr["number"]) 83 | 84 | return ret 85 | 86 | def get_pr(self, repo, pr_number): 87 | resp = requests.get( 88 | "{}/repos/{}/pulls/{}".format(self._url, repo, pr_number), 89 | auth=(self._user, self._token), 90 | ) 91 | jsonResp = json.loads(resp.content) 92 | return jsonResp 93 | 94 | def print_release_note(self, repo, since): 95 | # remove merge commits 96 | commits = self.get_commits(repo, since) 97 | commits = filter( 98 | lambda c: not c["commit"]["message"].startswith("Merge pull request"), commits 99 | ) 100 | pr_numbers = set() 101 | for commit in commits: 102 | numbers = self.to_pr_numbers(repo, commit) 103 | for pr in numbers: 104 | pr_numbers.add(pr) 105 | 106 | # dedupe pr numbers 107 | pr_numbers = sorted(list(pr_numbers)) 108 | 109 | for number in pr_numbers: 110 | pr = self.get_pr(repo, number) 111 | if "user" in pr: 112 | user = pr["user"]["login"] 113 | print( 114 | "* {} ([#{}]({}), [@{}](https://github.com/{}))".format( 115 | pr["title"], pr["number"], pr["html_url"], user, user 116 | ) 117 | ) 118 | 119 | 120 | def print_sha(args): 121 | version = args.version 122 | repo = args.repo 123 | print_header(repo, version) 124 | 125 | 126 | def print_notes(args): 127 | repo = args.repo 128 | since = args.since 129 | user = args.github_user 130 | token = args.github_token 131 | 132 | g = Github(user, token) 133 | g.print_release_note(repo, since) 134 | 135 | 136 | if __name__ == "__main__": 137 | parser = argparse.ArgumentParser(description="Generate release CHANGELOG") 138 | parser.add_argument( 139 | "--repo", 140 | metavar="repo", 141 | type=str, 142 | default="kubernetes-sigs/aws-fsx-csi-driver", 143 | help="the full github repository name", 144 | ) 145 | parser.add_argument( 146 | "--github-user", metavar="user", type=str, help="the github user for github api" 147 | ) 148 | parser.add_argument( 149 | "--github-token", metavar="token", type=str, help="the github token for github api" 150 | ) 151 | 152 | subParsers = parser.add_subparsers(title="subcommands", description="[note|sha]") 153 | 154 | noteParser = subParsers.add_parser("note", help="generate release notes") 155 | noteParser.add_argument( 156 | "--since", metavar="since", type=str, required=True, help="since version tag" 157 | ) 158 | noteParser.set_defaults(func=print_notes) 159 | 160 | shaParser = subParsers.add_parser("sha", help="generate SHA for released version tag") 161 | shaParser.add_argument( 162 | "--version", metavar="version", type=str, required=True, help="the version to release" 163 | ) 164 | shaParser.set_defaults(func=print_sha) 165 | 166 | args = parser.parse_args() 167 | args.func(args) 168 | -------------------------------------------------------------------------------- /hack/e2e/kops.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | OS_ARCH=$(go env GOOS)-amd64 6 | 7 | BASE_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")") 8 | source "${BASE_DIR}"/util.sh 9 | 10 | function kops_install() { 11 | INSTALL_PATH=${1} 12 | KOPS_VERSION=${2} 13 | if [[ -e "${INSTALL_PATH}"/kops ]]; then 14 | INSTALLED_KOPS_VERSION=$("${INSTALL_PATH}"/kops version) 15 | if [[ "$INSTALLED_KOPS_VERSION" == *"$KOPS_VERSION"* ]]; then 16 | echo "KOPS $INSTALLED_KOPS_VERSION already installed!" 17 | return 18 | fi 19 | fi 20 | KOPS_DOWNLOAD_URL=https://github.com/kubernetes/kops/releases/download/v${KOPS_VERSION}/kops-${OS_ARCH} 21 | curl -L -X GET "${KOPS_DOWNLOAD_URL}" -o "${INSTALL_PATH}"/kops 22 | chmod +x "${INSTALL_PATH}"/kops 23 | } 24 | 25 | function kops_create_cluster() { 26 | SSH_KEY_PATH=${1} 27 | CLUSTER_NAME=${2} 28 | BIN=${3} 29 | ZONES=${4} 30 | NODE_COUNT=${5} 31 | INSTANCE_TYPE=${6} 32 | K8S_VERSION=${7} 33 | CLUSTER_FILE=${8} 34 | KUBECONFIG=${9} 35 | KOPS_PATCH_FILE=${10} 36 | KOPS_PATCH_NODE_FILE=${11} 37 | KOPS_STATE_FILE=${12} 38 | AMI_ID=${13} 39 | 40 | generate_ssh_key "${SSH_KEY_PATH}" 41 | 42 | if kops_cluster_exists "${CLUSTER_NAME}" "${BIN}" "${KOPS_STATE_FILE}"; then 43 | loudecho "Replacing cluster $CLUSTER_NAME with $CLUSTER_FILE" 44 | ${BIN} replace --state "${KOPS_STATE_FILE}" -f "${CLUSTER_FILE}" 45 | else 46 | loudecho "Creating cluster $CLUSTER_NAME with $CLUSTER_FILE (dry run)" 47 | ${BIN} create cluster --state "${KOPS_STATE_FILE}" \ 48 | --ssh-public-key="${SSH_KEY_PATH}".pub \ 49 | --zones "${ZONES}" \ 50 | --node-count="${NODE_COUNT}" \ 51 | --node-size="${INSTANCE_TYPE}" \ 52 | --image="${AMI_ID}" \ 53 | --kubernetes-version="${K8S_VERSION}" \ 54 | --dry-run \ 55 | -o yaml \ 56 | "${CLUSTER_NAME}" > "${CLUSTER_FILE}" 57 | 58 | if test -f "$KOPS_PATCH_FILE"; then 59 | kops_patch_cluster_file "$CLUSTER_FILE" "$KOPS_PATCH_FILE" "Cluster" "" 60 | fi 61 | if test -f "$KOPS_PATCH_NODE_FILE"; then 62 | kops_patch_cluster_file "$CLUSTER_FILE" "$KOPS_PATCH_NODE_FILE" "InstanceGroup" "Node" 63 | fi 64 | 65 | loudecho "Creating cluster $CLUSTER_NAME with $CLUSTER_FILE" 66 | ${BIN} create --state "${KOPS_STATE_FILE}" -f "${CLUSTER_FILE}" 67 | kops create secret --state "${KOPS_STATE_FILE}" --name "${CLUSTER_NAME}" sshpublickey admin -i "${SSH_KEY_PATH}".pub 68 | fi 69 | 70 | loudecho "Updating cluster $CLUSTER_NAME with $CLUSTER_FILE" 71 | ${BIN} update cluster --state "${KOPS_STATE_FILE}" "${CLUSTER_NAME}" --yes 72 | 73 | loudecho "Exporting cluster ${CLUSTER_NAME} kubecfg to ${KUBECONFIG}" 74 | ${BIN} export kubecfg --state "${KOPS_STATE_FILE}" "${CLUSTER_NAME}" --admin --kubeconfig "${KUBECONFIG}" 75 | 76 | loudecho "Validating cluster ${CLUSTER_NAME}" 77 | ${BIN} validate cluster --state "${KOPS_STATE_FILE}" --wait 10m --kubeconfig "${KUBECONFIG}" 78 | return $? 79 | } 80 | 81 | function kops_cluster_exists() { 82 | CLUSTER_NAME=${1} 83 | BIN=${2} 84 | KOPS_STATE_FILE=${3} 85 | set +e 86 | if ${BIN} get cluster --state "${KOPS_STATE_FILE}" "${CLUSTER_NAME}"; then 87 | set -e 88 | return 0 89 | else 90 | set -e 91 | return 1 92 | fi 93 | } 94 | 95 | function kops_delete_cluster() { 96 | BIN=${1} 97 | CLUSTER_NAME=${2} 98 | KOPS_STATE_FILE=${3} 99 | loudecho "Deleting cluster ${CLUSTER_NAME}" 100 | ${BIN} delete cluster --name "${CLUSTER_NAME}" --state "${KOPS_STATE_FILE}" --yes 101 | } 102 | 103 | # TODO switch this to python, work exclusively with yaml, use kops toolbox 104 | # template/kops set?, all this hacking with jq stinks! 105 | function kops_patch_cluster_file() { 106 | CLUSTER_FILE=${1} # input must be yaml 107 | KOPS_PATCH_FILE=${2} # input must be yaml 108 | KIND=${3} # must be either Cluster or InstanceGroup 109 | ROLE=${4} # must be either Master or Node 110 | 111 | loudecho "Patching cluster $CLUSTER_NAME with $KOPS_PATCH_FILE" 112 | 113 | # Temporary intermediate files for patching, don't mutate CLUSTER_FILE until 114 | # the end 115 | CLUSTER_FILE_JSON=$CLUSTER_FILE.json 116 | CLUSTER_FILE_0=$CLUSTER_FILE.0 117 | CLUSTER_FILE_1=$CLUSTER_FILE.1 118 | 119 | # HACK convert the multiple yaml documents to an array of json objects 120 | yaml_to_json "$CLUSTER_FILE" "$CLUSTER_FILE_JSON" 121 | 122 | # Find the json objects to patch 123 | FILTER=".[] | select(.kind==\"$KIND\")" 124 | if [ -n "$ROLE" ]; then 125 | FILTER="$FILTER | select(.spec.role==\"$ROLE\")" 126 | fi 127 | jq "$FILTER" "$CLUSTER_FILE_JSON" > "$CLUSTER_FILE_0" 128 | 129 | # Patch only the json objects 130 | kubectl patch -f "$CLUSTER_FILE_0" --local --type merge --patch "$(cat "$KOPS_PATCH_FILE")" -o json > "$CLUSTER_FILE_1" 131 | mv "$CLUSTER_FILE_1" "$CLUSTER_FILE_0" 132 | 133 | # Delete the original json objects, add the patched 134 | # TODO Cluster must always be first? 135 | jq "del($FILTER)" "$CLUSTER_FILE_JSON" | jq ". + \$patched | sort" --slurpfile patched "$CLUSTER_FILE_0" > "$CLUSTER_FILE_1" 136 | mv "$CLUSTER_FILE_1" "$CLUSTER_FILE_0" 137 | 138 | # HACK convert the array of json objects to multiple yaml documents 139 | json_to_yaml "$CLUSTER_FILE_0" "$CLUSTER_FILE_1" 140 | mv "$CLUSTER_FILE_1" "$CLUSTER_FILE_0" 141 | 142 | # Done patching, overwrite original yaml CLUSTER_FILE 143 | mv "$CLUSTER_FILE_0" "$CLUSTER_FILE" # output is yaml 144 | 145 | # Clean up 146 | rm "$CLUSTER_FILE_JSON" 147 | } 148 | 149 | function yaml_to_json() { 150 | IN=${1} 151 | OUT=${2} 152 | kubectl patch -f "$IN" --local -p "{}" --type merge -o json | jq '.' -s > "$OUT" 153 | } 154 | 155 | function json_to_yaml() { 156 | IN=${1} 157 | OUT=${2} 158 | for ((i = 0; i < $(jq length "$IN"); i++)); do 159 | echo "---" >> "$OUT" 160 | jq ".[$i]" "$IN" | kubectl patch -f - --local -p "{}" --type merge -o yaml >> "$OUT" 161 | done 162 | } 163 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | VERSION?=v1.8.0 16 | 17 | PKG=sigs.k8s.io/aws-fsx-csi-driver 18 | GIT_COMMIT?=$(shell git rev-parse HEAD) 19 | BUILD_DATE?=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") 20 | 21 | LDFLAGS?="-X ${PKG}/pkg/driver.driverVersion=${VERSION} -X ${PKG}/pkg/cloud.driverVersion=${VERSION} -X ${PKG}/pkg/driver.gitCommit=${GIT_COMMIT} -X ${PKG}/pkg/driver.buildDate=${BUILD_DATE} -s -w" 22 | 23 | GO111MODULE=on 24 | GOPROXY=https://proxy.golang.org,direct 25 | GOPATH=$(shell go env GOPATH) 26 | GOOS=$(shell go env GOOS) 27 | GOBIN=$(shell pwd)/bin 28 | 29 | REGISTRY?=public.ecr.aws 30 | IMAGE?=$(REGISTRY)/aws-fsx-csi-driver 31 | TAG?=$(GIT_COMMIT) 32 | 33 | OUTPUT_TYPE?=docker 34 | 35 | OS?=linux 36 | ARCH?=amd64 37 | OSVERSION?=amazon 38 | AL_VERSION?=al23 39 | 40 | ALL_OS?=linux 41 | ALL_ARCH_linux?=amd64 arm64 42 | ALL_OSVERSION_linux?=amazon 43 | ALL_OS_ARCH_OSVERSION_linux=$(foreach arch, $(ALL_ARCH_linux), $(foreach osversion, ${ALL_OSVERSION_linux}, linux-$(arch)-${osversion})) 44 | 45 | ALL_OS_ARCH_OSVERSION=$(foreach os, $(ALL_OS), ${ALL_OS_ARCH_OSVERSION_${os}}) 46 | 47 | PLATFORM?=linux/amd64,linux/arm64 48 | 49 | # split words on hyphen, access by 1-index 50 | word-hyphen = $(word $2,$(subst -, ,$1)) 51 | 52 | .EXPORT_ALL_VARIABLES: 53 | 54 | .PHONY: linux/$(ARCH) bin/aws-fsx-csi-driver 55 | linux/$(ARCH): bin/aws-fsx-csi-driver 56 | bin/aws-fsx-csi-driver: | bin 57 | CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) go build -mod=mod -ldflags ${LDFLAGS} -o bin/aws-fsx-csi-driver ./cmd/ 58 | 59 | .PHONY: all 60 | all: all-image-docker 61 | 62 | # Builds all images and pushes them 63 | .PHONY: all-push 64 | all-push: create-manifest-and-images 65 | docker manifest push --purge $(IMAGE):$(TAG) 66 | 67 | .PHONY: create-manifest-and-images 68 | create-manifest-and-images: all-image-registry 69 | # sed expression: 70 | # LHS: match 0 or more not space characters 71 | # RHS: replace with $(IMAGE):$(TAG)-& where & is what was matched on LHS 72 | docker manifest create --amend $(IMAGE):$(TAG) $(shell echo $(ALL_OS_ARCH_OSVERSION) | sed -e "s~[^ ]*~$(IMAGE):$(TAG)\-&~g") 73 | 74 | .PHONY: all-image-docker 75 | all-image-docker: $(addprefix sub-image-docker-,$(ALL_OS_ARCH_OSVERSION_linux)) 76 | .PHONY: all-image-registry 77 | all-image-registry: $(addprefix sub-image-registry-,$(ALL_OS_ARCH_OSVERSION)) 78 | 79 | sub-image-%: 80 | $(MAKE) OUTPUT_TYPE=$(call word-hyphen,$*,1) OS=$(call word-hyphen,$*,2) ARCH=$(call word-hyphen,$*,3) OSVERSION=$(call word-hyphen,$*,4) image 81 | 82 | .PHONY: image 83 | image: .image-$(TAG)-$(OS)-$(ARCH)-$(OSVERSION) 84 | .image-$(TAG)-$(OS)-$(ARCH)-$(OSVERSION): 85 | docker buildx build \ 86 | --platform=$(OS)/$(ARCH) \ 87 | --progress=plain \ 88 | --target=$(OS)-$(OSVERSION) \ 89 | --output=type=$(OUTPUT_TYPE) \ 90 | -t=$(IMAGE):$(TAG)-$(OS)-$(ARCH)-$(OSVERSION) \ 91 | --build-arg=GOPROXY=$(GOPROXY) \ 92 | --build-arg=VERSION=$(VERSION) \ 93 | --build-arg=AL_VERSION=$(AL_VERSION) \ 94 | `./hack/provenance` \ 95 | . 96 | touch $@ 97 | 98 | .PHONY: clean 99 | clean: 100 | rm -rf .*image-* bin/ 101 | 102 | bin /tmp/helm: 103 | @mkdir -p $@ 104 | 105 | bin/helm: | /tmp/helm bin 106 | @curl -o /tmp/helm/helm.tar.gz -sSL https://get.helm.sh/helm-v3.5.3-${GOOS}-amd64.tar.gz 107 | @tar -zxf /tmp/helm/helm.tar.gz -C bin --strip-components=1 108 | @rm -rf /tmp/helm/* 109 | 110 | .PHONY: verify 111 | verify: 112 | ./hack/verify-all 113 | 114 | .PHONY: test 115 | test: 116 | go test -v -race ./pkg/... 117 | go test -v ./cmd/... 118 | go test -v ./tests/sanity/... 119 | 120 | .PHONY: test-e2e 121 | test-e2e: 122 | DRIVER_NAME=aws-fsx-csi-driver \ 123 | CONTAINER_NAME=fsx-plugin \ 124 | TEST_EXTRA_FLAGS='--cluster-name=$$CLUSTER_NAME' \ 125 | AWS_REGION=us-west-2 \ 126 | AWS_AVAILABILITY_ZONES=us-west-2a \ 127 | TEST_PATH=./tests/e2e/ \ 128 | GINKGO_FOCUS=".*" \ 129 | GINKGO_SKIP="subPath.should.be.able.to.unmount.after.the.subpath.directory.is.deleted|\[Disruptive\]|\[Serial\]" \ 130 | ./hack/e2e/run.sh 131 | 132 | .PHONY: generate-kustomize 133 | generate-kustomize: bin/helm 134 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/csidriver.yaml > ../../deploy/kubernetes/base/csidriver.yaml 135 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/node-daemonset.yaml > ../../deploy/kubernetes/base/node-daemonset.yaml 136 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/node-serviceaccount.yaml > ../../deploy/kubernetes/base/node-serviceaccount.yaml 137 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/controller-deployment.yaml > ../../deploy/kubernetes/base/controller-deployment.yaml 138 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/controller-serviceaccount.yaml > ../../deploy/kubernetes/base/controller-serviceaccount.yaml 139 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/clusterrole-csi-node.yaml > ../../deploy/kubernetes/base/clusterrole-csi-node.yaml 140 | cd charts/aws-fsx-csi-driver && ../../bin/helm template kustomize . -s templates/clusterrolebinding-csi-node.yaml > ../../deploy/kubernetes/base/clusterrolebinding-csi-node.yaml 141 | 142 | $(MAKE) remove-namespace-kustomize-files 143 | 144 | .PHONY: remove-namespace-kustomize-files 145 | remove-namespace-kustomize-files: 146 | ls deploy/kubernetes/base/* | grep -v 'kustomization\.yaml' | xargs sed -i '/namespace:/d' 147 | 148 | -------------------------------------------------------------------------------- /tests/e2e/conformance_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package e2e 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | . "github.com/onsi/ginkgo/v2" 22 | v1 "k8s.io/api/core/v1" 23 | "k8s.io/apimachinery/pkg/util/sets" 24 | "k8s.io/kubernetes/test/e2e/framework" 25 | storageframework "k8s.io/kubernetes/test/e2e/storage/framework" 26 | "k8s.io/kubernetes/test/e2e/storage/testsuites" 27 | fsx "sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud" 28 | fsxcsidriver "sigs.k8s.io/aws-fsx-csi-driver/pkg/driver" 29 | ) 30 | 31 | type fsxVolume struct { 32 | c *cloud 33 | fileSystemId string 34 | dnsName string 35 | } 36 | 37 | func (v *fsxVolume) DeleteVolume(ctx context.Context) { 38 | err := v.c.DeleteFileSystem(ctx, v.fileSystemId) 39 | if err != nil { 40 | Fail(fmt.Sprintf("failed to delete filesystem %s", err)) 41 | } 42 | } 43 | 44 | type fsxDriver struct { 45 | driverName string 46 | } 47 | 48 | var _ storageframework.TestDriver = &fsxDriver{} 49 | var _ storageframework.PreprovisionedPVTestDriver = &fsxDriver{} 50 | 51 | func InitFSxCSIDriver() storageframework.TestDriver { 52 | return &fsxDriver{ 53 | driverName: fsxcsidriver.DriverName, 54 | } 55 | } 56 | 57 | func (e *fsxDriver) GetDriverInfo() *storageframework.DriverInfo { 58 | return &storageframework.DriverInfo{ 59 | Name: e.driverName, 60 | SupportedFsType: sets.NewString(""), 61 | SupportedMountOption: sets.NewString("flock", "ro"), 62 | Capabilities: map[storageframework.Capability]bool{ 63 | storageframework.CapPersistence: true, 64 | storageframework.CapExec: true, 65 | storageframework.CapMultiPODs: true, 66 | storageframework.CapRWX: true, 67 | }, 68 | } 69 | } 70 | 71 | func (e *fsxDriver) SkipUnsupportedTest(storageframework.TestPattern) {} 72 | 73 | func (e *fsxDriver) PrepareTest(ctx context.Context, f *framework.Framework) *storageframework.PerTestConfig { 74 | return &storageframework.PerTestConfig{ 75 | Driver: e, 76 | Prefix: "fsx", 77 | Framework: f, 78 | } 79 | } 80 | 81 | func (e *fsxDriver) CreateVolume(ctx context.Context, config *storageframework.PerTestConfig, volType storageframework.TestVolType) storageframework.TestVolume { 82 | c := NewCloud(*region) 83 | instance, err := c.getNodeInstance(*clusterName) 84 | if err != nil { 85 | Fail(fmt.Sprintf("failed to get node instance %v", err)) 86 | } 87 | securityGroupIds := getSecurityGroupIds(instance) 88 | subnetId := *instance.SubnetId 89 | 90 | options := &fsx.FileSystemOptions{ 91 | CapacityGiB: 3600, 92 | SubnetId: subnetId, 93 | SecurityGroupIds: securityGroupIds, 94 | FileSystemTypeVersion: "2.15", 95 | } 96 | ns := config.Framework.Namespace.Name 97 | volumeName := fmt.Sprintf("fsx-e2e-test-volume-%s", ns) 98 | fs, err := c.CreateFileSystem(ctx, volumeName, options) 99 | if err != nil { 100 | Fail(fmt.Sprintf("failed to created filesystem %s", err)) 101 | } 102 | 103 | err = c.WaitForFileSystemAvailable(ctx, fs.FileSystemId) 104 | if err != nil { 105 | Fail(fmt.Sprintf("failed to wait on filesystem %s", err)) 106 | } 107 | 108 | return &fsxVolume{ 109 | c: c, 110 | fileSystemId: fs.FileSystemId, 111 | dnsName: fs.DnsName, 112 | } 113 | } 114 | 115 | func (e *fsxDriver) GetPersistentVolumeSource(readOnly bool, fsType string, volume storageframework.TestVolume) (*v1.PersistentVolumeSource, *v1.VolumeNodeAffinity) { 116 | v := volume.(*fsxVolume) 117 | pvSource := v1.PersistentVolumeSource{ 118 | CSI: &v1.CSIPersistentVolumeSource{ 119 | Driver: e.driverName, 120 | VolumeHandle: v.fileSystemId, 121 | VolumeAttributes: map[string]string{ 122 | "dnsname": v.dnsName, 123 | }, 124 | }, 125 | } 126 | return &pvSource, nil 127 | } 128 | 129 | // TOOD: uncomment for testing dynamic provisioning 130 | 131 | //var _ testsuites.DynamicPVTestDriver = &fsxDriver{} 132 | //func (e *fsxDriver) GetDynamicProvisionStorageClass(config *testsuites.PerTestConfig, fsType string) *storagev1.StorageClass { 133 | // c := NewCloud(*region) 134 | // instance, err := c.getNodeInstance(*clusterName) 135 | // if err != nil { 136 | // Fail(fmt.Sprintf("failed to get node instance %v", err)) 137 | // } 138 | // securityGroupIds := getSecurityGroupIds(instance) 139 | // subnetId := *instance.SubnetId 140 | // 141 | // provisioner := e.driverName 142 | // parameters := map[string]string{ 143 | // "subnetId": subnetId, 144 | // "securityGroupIds": strings.Join(securityGroupIds, ","), 145 | // } 146 | // 147 | // ns := config.Framework.Namespace.Name 148 | // suffix := fmt.Sprintf("%s-sc", e.driverName) 149 | // 150 | // return &storagev1.StorageClass{ 151 | // TypeMeta: metav1.TypeMeta{ 152 | // Kind: "StorageClass", 153 | // }, 154 | // ObjectMeta: metav1.ObjectMeta{ 155 | // // Name must be unique, so let's base it on namespace name and use GenerateName 156 | // Name: names.SimpleNameGenerator.GenerateName(ns + "-" + suffix), 157 | // }, 158 | // Provisioner: provisioner, 159 | // Parameters: parameters, 160 | // } 161 | //} 162 | 163 | // List of testSuites to be executed in below loop 164 | var csiTestSuites = []func() storageframework.TestSuite{ 165 | testsuites.InitVolumesTestSuite, 166 | testsuites.InitVolumeIOTestSuite, 167 | testsuites.InitVolumeModeTestSuite, 168 | testsuites.InitSubPathTestSuite, 169 | testsuites.InitProvisioningTestSuite, 170 | //testsuites.InitSnapshottableTestSuite, 171 | testsuites.InitVolumeExpandTestSuite, 172 | testsuites.InitMultiVolumeTestSuite, 173 | } 174 | 175 | var _ = Describe("FSx CSI Driver Conformance", func() { 176 | driver := InitFSxCSIDriver() 177 | 178 | args := storageframework.GetDriverNameWithFeatureTags(driver) 179 | args = append(args, func() { 180 | storageframework.DefineTestSuites(driver, csiTestSuites) 181 | }) 182 | framework.Context(args...) 183 | }) 184 | -------------------------------------------------------------------------------- /pkg/driver/mocks/mock_cloud.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud (interfaces: Cloud) 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -package=mocks -destination=./pkg/driver/mocks/mock_cloud.go --build_flags=--mod=mod sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud Cloud 7 | // 8 | 9 | // Package mocks is a generated GoMock package. 10 | package mocks 11 | 12 | import ( 13 | context "context" 14 | reflect "reflect" 15 | 16 | gomock "go.uber.org/mock/gomock" 17 | cloud "sigs.k8s.io/aws-fsx-csi-driver/pkg/cloud" 18 | ) 19 | 20 | // MockCloud is a mock of Cloud interface. 21 | type MockCloud struct { 22 | ctrl *gomock.Controller 23 | recorder *MockCloudMockRecorder 24 | isgomock struct{} 25 | } 26 | 27 | // MockCloudMockRecorder is the mock recorder for MockCloud. 28 | type MockCloudMockRecorder struct { 29 | mock *MockCloud 30 | } 31 | 32 | // NewMockCloud creates a new mock instance. 33 | func NewMockCloud(ctrl *gomock.Controller) *MockCloud { 34 | mock := &MockCloud{ctrl: ctrl} 35 | mock.recorder = &MockCloudMockRecorder{mock} 36 | return mock 37 | } 38 | 39 | // EXPECT returns an object that allows the caller to indicate expected use. 40 | func (m *MockCloud) EXPECT() *MockCloudMockRecorder { 41 | return m.recorder 42 | } 43 | 44 | // CreateFileSystem mocks base method. 45 | func (m *MockCloud) CreateFileSystem(ctx context.Context, volumeName string, fileSystemOptions *cloud.FileSystemOptions) (*cloud.FileSystem, error) { 46 | m.ctrl.T.Helper() 47 | ret := m.ctrl.Call(m, "CreateFileSystem", ctx, volumeName, fileSystemOptions) 48 | ret0, _ := ret[0].(*cloud.FileSystem) 49 | ret1, _ := ret[1].(error) 50 | return ret0, ret1 51 | } 52 | 53 | // CreateFileSystem indicates an expected call of CreateFileSystem. 54 | func (mr *MockCloudMockRecorder) CreateFileSystem(ctx, volumeName, fileSystemOptions any) *gomock.Call { 55 | mr.mock.ctrl.T.Helper() 56 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileSystem", reflect.TypeOf((*MockCloud)(nil).CreateFileSystem), ctx, volumeName, fileSystemOptions) 57 | } 58 | 59 | // DeleteFileSystem mocks base method. 60 | func (m *MockCloud) DeleteFileSystem(ctx context.Context, fileSystemId string) error { 61 | m.ctrl.T.Helper() 62 | ret := m.ctrl.Call(m, "DeleteFileSystem", ctx, fileSystemId) 63 | ret0, _ := ret[0].(error) 64 | return ret0 65 | } 66 | 67 | // DeleteFileSystem indicates an expected call of DeleteFileSystem. 68 | func (mr *MockCloudMockRecorder) DeleteFileSystem(ctx, fileSystemId any) *gomock.Call { 69 | mr.mock.ctrl.T.Helper() 70 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFileSystem", reflect.TypeOf((*MockCloud)(nil).DeleteFileSystem), ctx, fileSystemId) 71 | } 72 | 73 | // DescribeFileSystem mocks base method. 74 | func (m *MockCloud) DescribeFileSystem(ctx context.Context, fileSystemId string) (*cloud.FileSystem, error) { 75 | m.ctrl.T.Helper() 76 | ret := m.ctrl.Call(m, "DescribeFileSystem", ctx, fileSystemId) 77 | ret0, _ := ret[0].(*cloud.FileSystem) 78 | ret1, _ := ret[1].(error) 79 | return ret0, ret1 80 | } 81 | 82 | // DescribeFileSystem indicates an expected call of DescribeFileSystem. 83 | func (mr *MockCloudMockRecorder) DescribeFileSystem(ctx, fileSystemId any) *gomock.Call { 84 | mr.mock.ctrl.T.Helper() 85 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeFileSystem", reflect.TypeOf((*MockCloud)(nil).DescribeFileSystem), ctx, fileSystemId) 86 | } 87 | 88 | // FindFileSystemByVolumeName mocks base method. 89 | func (m *MockCloud) FindFileSystemByVolumeName(ctx context.Context, volumeName string) (*cloud.FileSystem, error) { 90 | m.ctrl.T.Helper() 91 | ret := m.ctrl.Call(m, "FindFileSystemByVolumeName", ctx, volumeName) 92 | ret0, _ := ret[0].(*cloud.FileSystem) 93 | ret1, _ := ret[1].(error) 94 | return ret0, ret1 95 | } 96 | 97 | // FindFileSystemByVolumeName indicates an expected call of FindFileSystemByVolumeName. 98 | func (mr *MockCloudMockRecorder) FindFileSystemByVolumeName(ctx, volumeName any) *gomock.Call { 99 | mr.mock.ctrl.T.Helper() 100 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindFileSystemByVolumeName", reflect.TypeOf((*MockCloud)(nil).FindFileSystemByVolumeName), ctx, volumeName) 101 | } 102 | 103 | // ResizeFileSystem mocks base method. 104 | func (m *MockCloud) ResizeFileSystem(ctx context.Context, fileSystemId string, newSizeGiB int32) (int32, error) { 105 | m.ctrl.T.Helper() 106 | ret := m.ctrl.Call(m, "ResizeFileSystem", ctx, fileSystemId, newSizeGiB) 107 | ret0, _ := ret[0].(int32) 108 | ret1, _ := ret[1].(error) 109 | return ret0, ret1 110 | } 111 | 112 | // ResizeFileSystem indicates an expected call of ResizeFileSystem. 113 | func (mr *MockCloudMockRecorder) ResizeFileSystem(ctx, fileSystemId, newSizeGiB any) *gomock.Call { 114 | mr.mock.ctrl.T.Helper() 115 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeFileSystem", reflect.TypeOf((*MockCloud)(nil).ResizeFileSystem), ctx, fileSystemId, newSizeGiB) 116 | } 117 | 118 | // WaitForFileSystemAvailable mocks base method. 119 | func (m *MockCloud) WaitForFileSystemAvailable(ctx context.Context, fileSystemId string) error { 120 | m.ctrl.T.Helper() 121 | ret := m.ctrl.Call(m, "WaitForFileSystemAvailable", ctx, fileSystemId) 122 | ret0, _ := ret[0].(error) 123 | return ret0 124 | } 125 | 126 | // WaitForFileSystemAvailable indicates an expected call of WaitForFileSystemAvailable. 127 | func (mr *MockCloudMockRecorder) WaitForFileSystemAvailable(ctx, fileSystemId any) *gomock.Call { 128 | mr.mock.ctrl.T.Helper() 129 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForFileSystemAvailable", reflect.TypeOf((*MockCloud)(nil).WaitForFileSystemAvailable), ctx, fileSystemId) 130 | } 131 | 132 | // WaitForFileSystemResize mocks base method. 133 | func (m *MockCloud) WaitForFileSystemResize(ctx context.Context, fileSystemId string, resizeGiB int32) error { 134 | m.ctrl.T.Helper() 135 | ret := m.ctrl.Call(m, "WaitForFileSystemResize", ctx, fileSystemId, resizeGiB) 136 | ret0, _ := ret[0].(error) 137 | return ret0 138 | } 139 | 140 | // WaitForFileSystemResize indicates an expected call of WaitForFileSystemResize. 141 | func (mr *MockCloudMockRecorder) WaitForFileSystemResize(ctx, fileSystemId, resizeGiB any) *gomock.Call { 142 | mr.mock.ctrl.T.Helper() 143 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForFileSystemResize", reflect.TypeOf((*MockCloud)(nil).WaitForFileSystemResize), ctx, fileSystemId, resizeGiB) 144 | } 145 | -------------------------------------------------------------------------------- /tests/e2e/dynamic_provisioning_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package e2e 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "strings" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | "k8s.io/api/core/v1" 24 | clientset "k8s.io/client-go/kubernetes" 25 | "k8s.io/kubernetes/test/e2e/framework" 26 | admissionapi "k8s.io/pod-security-admission/api" 27 | "sigs.k8s.io/aws-fsx-csi-driver/tests/e2e/driver" 28 | "sigs.k8s.io/aws-fsx-csi-driver/tests/e2e/testsuites" 29 | ) 30 | 31 | var _ = Describe("[fsx-csi-e2e] Dynamic Provisioning", func() { 32 | f := framework.NewDefaultFramework("fsx") 33 | f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged 34 | 35 | var ( 36 | cs clientset.Interface 37 | ns *v1.Namespace 38 | dvr driver.PVTestDriver 39 | cloud *cloud 40 | subnetId string 41 | securityGroupIds []string 42 | ) 43 | 44 | BeforeEach(func() { 45 | cs = f.ClientSet 46 | ns = f.Namespace 47 | dvr = driver.InitFSxCSIDriver() 48 | cloud = NewCloud(*region) 49 | instance, err := cloud.getNodeInstance(*clusterName) 50 | if err != nil { 51 | Fail(fmt.Sprintf("failed to get node instance %v", err)) 52 | } 53 | securityGroupIds = getSecurityGroupIds(instance) 54 | subnetId = *instance.SubnetId 55 | }) 56 | 57 | It("should create a volume on demand with subnetId and security groups", func() { 58 | log.Printf("Using subnet ID %s security group ID %s", subnetId, securityGroupIds) 59 | pods := []testsuites.PodDetails{ 60 | { 61 | Cmd: "echo 'hello world' > /mnt/test-1/data && grep 'hello world' /mnt/test-1/data", 62 | Volumes: []testsuites.VolumeDetails{ 63 | { 64 | Parameters: map[string]string{ 65 | "subnetId": subnetId, 66 | "securityGroupIds": strings.Join(securityGroupIds, ","), 67 | "fileSystemTypeVersion": "2.15", 68 | }, 69 | ClaimSize: "3600Gi", 70 | VolumeMount: testsuites.VolumeMountDetails{ 71 | NameGenerate: "test-volume-", 72 | MountPathGenerate: "/mnt/test-", 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | test := testsuites.DynamicallyProvisionedCmdVolumeTest{ 79 | CSIDriver: dvr, 80 | Pods: pods, 81 | } 82 | test.Run(cs, ns) 83 | }) 84 | 85 | It("should create a volume on demand with flock mount option", func() { 86 | log.Printf("Using subnet ID %s security group ID %s", subnetId, securityGroupIds) 87 | pods := []testsuites.PodDetails{ 88 | { 89 | Cmd: "echo 'hello world' > /mnt/test-1/data && grep 'hello world' /mnt/test-1/data", 90 | Volumes: []testsuites.VolumeDetails{ 91 | { 92 | Parameters: map[string]string{ 93 | "subnetId": subnetId, 94 | "securityGroupIds": strings.Join(securityGroupIds, ","), 95 | "fileSystemTypeVersion": "2.15", 96 | }, 97 | MountOptions: []string{"flock"}, 98 | ClaimSize: "1200Gi", 99 | VolumeMount: testsuites.VolumeMountDetails{ 100 | NameGenerate: "test-volume-", 101 | MountPathGenerate: "/mnt/test-", 102 | }, 103 | }, 104 | }, 105 | }, 106 | } 107 | test := testsuites.DynamicallyProvisionedCmdVolumeTest{ 108 | CSIDriver: dvr, 109 | Pods: pods, 110 | } 111 | test.Run(cs, ns) 112 | }) 113 | }) 114 | 115 | var _ = Describe("[fsx-csi-e2e] Dynamic Provisioning with s3 data repository", func() { 116 | f := framework.NewDefaultFramework("fsx") 117 | f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged 118 | 119 | var ( 120 | cs clientset.Interface 121 | ns *v1.Namespace 122 | dvr driver.PVTestDriver 123 | cloud *cloud 124 | subnetId string 125 | securityGroupIds []string 126 | bucketName string 127 | ) 128 | 129 | BeforeEach(func() { 130 | cs = f.ClientSet 131 | ns = f.Namespace 132 | dvr = driver.InitFSxCSIDriver() 133 | cloud = NewCloud(*region) 134 | instance, err := cloud.getNodeInstance(*clusterName) 135 | if err != nil { 136 | Fail(fmt.Sprintf("failed to get node instance %v", err)) 137 | } 138 | securityGroupIds = getSecurityGroupIds(instance) 139 | subnetId = *instance.SubnetId 140 | 141 | bucketName = fmt.Sprintf("fsx-e2e-%s", ns.Name) 142 | fmt.Println("name: " + bucketName) 143 | err = cloud.createS3Bucket(bucketName, *region) 144 | if err != nil { 145 | Fail(fmt.Sprintf("failed to create s3 bucket %v", err)) 146 | } 147 | }) 148 | 149 | AfterEach(func() { 150 | err := cloud.deleteS3Bucket(bucketName) 151 | if err != nil { 152 | Fail(fmt.Sprintf("failed to delete s3 bucket %v", err)) 153 | } 154 | }) 155 | 156 | It("should create a volume on demand with s3 as data repository", func() { 157 | log.Printf("Using subnet ID %s security group ID %s", subnetId, securityGroupIds) 158 | pods := []testsuites.PodDetails{ 159 | { 160 | Cmd: "echo 'hello world' > /mnt/test-1/data && grep 'hello world' /mnt/test-1/data", 161 | Volumes: []testsuites.VolumeDetails{ 162 | { 163 | Parameters: map[string]string{ 164 | "subnetId": subnetId, 165 | "securityGroupIds": strings.Join(securityGroupIds, ","), 166 | "autoImportPolicy": "NONE", 167 | "s3ImportPath": fmt.Sprintf("s3://%s", bucketName), 168 | "s3ExportPath": fmt.Sprintf("s3://%s/export", bucketName), 169 | "fileSystemTypeVersion": "2.15", 170 | }, 171 | ClaimSize: "3600Gi", 172 | VolumeMount: testsuites.VolumeMountDetails{ 173 | NameGenerate: "test-volume-", 174 | MountPathGenerate: "/mnt/test-", 175 | }, 176 | }, 177 | }, 178 | }, 179 | } 180 | test := testsuites.DynamicallyProvisionedCmdVolumeTest{ 181 | CSIDriver: dvr, 182 | Pods: pods, 183 | } 184 | test.Run(cs, ns) 185 | }) 186 | 187 | }) 188 | -------------------------------------------------------------------------------- /charts/aws-fsx-csi-driver/templates/node-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: fsx-csi-node 5 | labels: 6 | {{- include "aws-fsx-csi-driver.labels" . | nindent 4 }} 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: fsx-csi-node 11 | {{- include "aws-fsx-csi-driver.selectorLabels" . | nindent 6 }} 12 | {{- with .Values.node.updateStrategy }} 13 | updateStrategy: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | template: 17 | metadata: 18 | {{- with .Values.node.podAnnotations }} 19 | annotations: 20 | {{- toYaml . | nindent 8 }} 21 | {{- end }} 22 | labels: 23 | app: fsx-csi-node 24 | {{- include "aws-fsx-csi-driver.labels" . | nindent 8 }} 25 | {{- if .Values.node.podLabels }} 26 | {{- toYaml .Values.node.podLabels | nindent 8 }} 27 | {{- end }} 28 | spec: 29 | {{- if .Values.imagePullSecrets }} 30 | imagePullSecrets: 31 | {{- range .Values.imagePullSecrets }} 32 | - name: {{ . }} 33 | {{- end }} 34 | {{- end }} 35 | nodeSelector: 36 | kubernetes.io/os: linux 37 | {{- with .Values.node.nodeSelector }} 38 | {{- toYaml . | nindent 8 }} 39 | {{- end }} 40 | dnsPolicy: {{ .Values.node.dnsPolicy }} 41 | {{- with .Values.node.dnsConfig }} 42 | dnsConfig: {{- toYaml . | nindent 8 }} 43 | {{- end }} 44 | serviceAccountName: {{ .Values.node.serviceAccount.name }} 45 | terminationGracePeriodSeconds: {{ .Values.node.terminationGracePeriodSeconds }} 46 | priorityClassName: system-node-critical 47 | tolerations: 48 | {{- if .Values.node.tolerateAllTaints }} 49 | - operator: Exists 50 | {{- else }} 51 | {{- with .Values.node.tolerations }} 52 | {{- toYaml . | nindent 8 }} 53 | {{- end }} 54 | - key: "fsx.csi.aws.com/agent-not-ready" 55 | operator: "Exists" 56 | {{- end }} 57 | {{- with .Values.node.affinity }} 58 | affinity: {{- toYaml . | nindent 8 }} 59 | {{- end }} 60 | containers: 61 | - name: fsx-plugin 62 | securityContext: 63 | privileged: true 64 | image: {{ printf "%s%s:%s" (default "" .Values.image.containerRegistry) .Values.image.repository (default (printf "v%s" .Chart.AppVersion) (.Values.image.tag | toString)) }} 65 | imagePullPolicy: {{ .Values.image.pullPolicy }} 66 | args: 67 | - --mode={{ .Values.node.mode }} 68 | - --endpoint=$(CSI_ENDPOINT) 69 | - --logging-format={{ .Values.node.loggingFormat }} 70 | - --v={{ .Values.node.logLevel }} 71 | env: 72 | - name: CSI_ENDPOINT 73 | value: unix:/csi/csi.sock 74 | - name: CSI_NODE_NAME 75 | valueFrom: 76 | fieldRef: 77 | fieldPath: spec.nodeName 78 | {{- with .Values.node.region }} 79 | - name: AWS_REGION 80 | value: {{ . }} 81 | {{- end }} 82 | volumeMounts: 83 | - name: kubelet-dir 84 | mountPath: /var/lib/kubelet 85 | mountPropagation: "Bidirectional" 86 | - name: plugin-dir 87 | mountPath: /csi 88 | ports: 89 | - name: healthz 90 | containerPort: 9810 91 | protocol: TCP 92 | livenessProbe: 93 | httpGet: 94 | path: /healthz 95 | port: healthz 96 | initialDelaySeconds: 10 97 | timeoutSeconds: 3 98 | periodSeconds: 2 99 | failureThreshold: 5 100 | lifecycle: 101 | preStop: 102 | exec: 103 | command: [ "/bin/aws-fsx-csi-driver", "pre-stop-hook" ] 104 | {{- with .Values.node.resources }} 105 | resources: 106 | {{- toYaml . | nindent 12 }} 107 | {{- end }} 108 | - name: node-driver-registrar 109 | image: {{ printf "%s%s:%s" (default "" .Values.image.containerRegistry) .Values.sidecars.nodeDriverRegistrar.image.repository .Values.sidecars.nodeDriverRegistrar.image.tag }} 110 | imagePullPolicy: {{ .Values.sidecars.nodeDriverRegistrar.image.pullPolicy }} 111 | args: 112 | - --csi-address=$(ADDRESS) 113 | - --kubelet-registration-path=$(DRIVER_REG_SOCK_PATH) 114 | - --v={{ .Values.sidecars.nodeDriverRegistrar.logLevel }} 115 | env: 116 | - name: ADDRESS 117 | value: /csi/csi.sock 118 | - name: DRIVER_REG_SOCK_PATH 119 | value: /var/lib/kubelet/plugins/fsx.csi.aws.com/csi.sock 120 | - name: KUBE_NODE_NAME 121 | valueFrom: 122 | fieldRef: 123 | fieldPath: spec.nodeName 124 | volumeMounts: 125 | - name: plugin-dir 126 | mountPath: /csi 127 | - name: registration-dir 128 | mountPath: /registration 129 | {{- with default .Values.node.resources .Values.sidecars.nodeDriverRegistrar.resources }} 130 | resources: 131 | {{- toYaml . | nindent 12 }} 132 | {{- end }} 133 | - name: liveness-probe 134 | image: {{ printf "%s%s:%s" (default "" .Values.image.containerRegistry) .Values.sidecars.livenessProbe.image.repository .Values.sidecars.livenessProbe.image.tag }} 135 | imagePullPolicy: {{ .Values.sidecars.livenessProbe.image.pullPolicy }} 136 | args: 137 | - --csi-address=/csi/csi.sock 138 | - --health-port=9810 139 | volumeMounts: 140 | - mountPath: /csi 141 | name: plugin-dir 142 | {{- with default .Values.node.resources .Values.sidecars.livenessProbe.resources }} 143 | resources: 144 | {{- toYaml . | nindent 12 }} 145 | {{- end }} 146 | volumes: 147 | - name: kubelet-dir 148 | hostPath: 149 | path: /var/lib/kubelet 150 | type: Directory 151 | - name: registration-dir 152 | hostPath: 153 | path: /var/lib/kubelet/plugins_registry/ 154 | type: Directory 155 | - name: plugin-dir 156 | hostPath: 157 | path: /var/lib/kubelet/plugins/fsx.csi.aws.com/ 158 | type: DirectoryOrCreate 159 | --------------------------------------------------------------------------------