Repository: topolvm/topolvm
Pull request #592 (2022-11-10 12:56:22)

topolvm.io
This is a merge PR for reducing the number of commits for #539

- #539
- #168
- #589

The below commands created this PR:

```
$ git checkout main
$ git pull
$ git checkout topolvm.io
$ git merge main
$ git push
$ git diff main > /tmp/topolvm.io.patch
$ git checkout main
$ git co -b topolvm.io4
$ git apply /tmp/topolvm.io.patch
$ git add .
$ git commit -m "topolvm.io"
$ git push
```

nbalacha: What I usually do when I need to change something in a commit is the following:
git reset --soft HEAD~<num-commits>
Make the changes and git add
git commit again
git push -u origin <branch name> --force

This will overwrite the branch head so I can use the same PR. It may be a way to proceed with these changes instead of creating a new PR.
bells17: > What I usually do when I need to change something in a commit is the following:

Thank you. I never tried the `git reset --soft` command.
I'll try these commands the next time.

peng225: LGTM. I will approve it after you squashed it.

Patch:
diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml
index 9be8729d..ca434602 100644
--- a/.github/workflows/helm.yaml
+++ b/.github/workflows/helm.yaml
@@ -8,7 +8,7 @@ on:
 
 jobs:
   lint:
-    runs-on: "ubuntu-18.04"
+    runs-on: "ubuntu-20.04"
 
     steps:
       - name: "Checkout"
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 50cbd24b..125ab768 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -7,7 +7,7 @@ on:
 jobs:
   build:
     name: "build"
-    runs-on: "ubuntu-18.04"
+    runs-on: "ubuntu-20.04"
     steps:
       - uses: actions/checkout@v3
       - uses: actions/setup-go@v3
@@ -25,15 +25,22 @@ jobs:
       - run: make setup
       - run: make check-uncommitted
       - run: make test
+      - run: make groupname-test
       - run: sudo -E env PATH=${PATH} go test -count=1 -race -v ./lvmd ./driver ./filesystem
       - run: make image
 
   example:
     name: "example"
-    runs-on: "ubuntu-18.04"
+    runs-on: "ubuntu-20.04"
     defaults:
       run:
         working-directory: "example"
+    strategy:
+      fail-fast: false
+      matrix:
+        use_legacy: ["", "true"]
+    env:
+      USE_LEGACY: ${{ matrix.use_legacy }}
     steps:
       - uses: actions/checkout@v3
       - uses: actions/setup-go@v3
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 4fa8bc4b..5277b0b0 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -6,7 +6,7 @@ on:
 jobs:
   release:
     name: "release"
-    runs-on: "ubuntu-18.04"
+    runs-on: "ubuntu-20.04"
     steps:
       - name: "Validate Release Version"
         id: check_version
diff --git a/Makefile b/Makefile
index e8c9823b..6beec530 100644
--- a/Makefile
+++ b/Makefile
@@ -7,6 +7,7 @@ PROTOC_VERSION=21.2
 KIND_VERSION=v0.14.0
 HELM_VERSION=3.9.0
 HELM_DOCS_VERSION=1.11.0
+YQ_VERSION=4.18.1
 GINKGO_VERSION := $(shell awk '/github.com\/onsi\/ginkgo/ {print substr($$2, 2)}' go.mod)
 
 SUDO := sudo
@@ -38,6 +39,22 @@ PROTOC_GEN_GO_GRPC_VERSION := $(shell awk '/google.golang.org\/grpc\/cmd\/protoc
 SHELL = /bin/bash
 .SHELLFLAGS = -e -o pipefail -c
 
+define CRD_TEMPLATE
+{{ if not .Values.useLegacy }}
+%s
+{{ end }}
+
+endef
+export CRD_TEMPLATE
+
+define LEGACY_CRD_TEMPLATE
+{{ if .Values.useLegacy }}
+%s
+{{ end }}
+
+endef
+export LEGACY_CRD_TEMPLATE
+
 ##@ General
 
 # The help target prints out all targets with their descriptions organized
@@ -82,18 +99,26 @@ PROTOBUF_GEN = csi/csi.pb.go csi/csi_grpc.pb.go \
 	lvmd/proto/lvmd.pb.go lvmd/proto/lvmd_grpc.pb.go docs/lvmd-protocol.md
 
 .PHONY: manifests
-manifests: ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
+manifests: generate-legacy-api ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
 	$(CONTROLLER_GEN) \
 		crd:crdVersions=v1 \
 		rbac:roleName=topolvm-controller \
 		webhook \
 		paths="./api/...;./controllers;./hook;./driver/k8s;./pkg/..." \
 		output:crd:artifacts:config=config/crd/bases
+	$(BINDIR)/yq eval 'del(.status)' config/crd/bases/topolvm.io_logicalvolumes.yaml | xargs -d"	" printf "$$CRD_TEMPLATE" > charts/topolvm/templates/crds/topolvm.io_logicalvolumes.yaml
+	$(BINDIR)/yq eval 'del(.status)' config/crd/bases/topolvm.cybozu.com_logicalvolumes.yaml | xargs -d"	" printf "$$LEGACY_CRD_TEMPLATE" > charts/topolvm/templates/crds/topolvm.cybozu.com_logicalvolumes.yaml
 
 .PHONY: generate-api ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
 generate-api: 
 	$(CONTROLLER_GEN) object:headerFile="./hack/boilerplate.go.txt" paths="./api/..."
 
+.PHONY: generate-legacy-api
+generate-legacy-api: ## Generate legacy api code.
+	mkdir -p api/legacy/v1
+	cp -r api/v1/* api/legacy/v1
+	sed -i -e 's/topolvm.io/topolvm.cybozu.com/g' api/legacy/v1/groupversion_info.go
+
 .PHONY: generate-helm-docs
 generate-helm-docs:
 	./bin/helm-docs -c charts/topolvm/
@@ -117,7 +142,14 @@ test: lint ## Run lint and unit tests.
 	go install ./...
 
 	mkdir -p $(ENVTEST_ASSETS_DIR)
-	source <($(BINDIR)/setup-envtest use $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p env); GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn go test -race -v ./...
+	source <($(BINDIR)/setup-envtest use $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p env); GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn go test -count=1 -race -v ./...
+
+groupname-test: ## Run unit tests that depends on the groupname.
+	go install ./...
+
+	mkdir -p $(ENVTEST_ASSETS_DIR)
+	source <($(BINDIR)/setup-envtest use $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p env); GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn TEST_LEGACY=true go test -count=1 -race -v ./client/*
+	TEST_LEGACY=true go test -count=1 -race -v ./constants*.go
 
 .PHONY: clean
 clean: ## Clean working directory.
@@ -138,7 +170,7 @@ build/hypertopolvm: $(GO_FILES)
 	mkdir -p build
 	go build -o $@ -ldflags "-w -s -X github.com/topolvm/topolvm.Version=$(TOPOLVM_VERSION)" ./pkg/hypertopolvm
 
-build/lvmd: $(GO_FILES)
+build/lvmd:
 	mkdir -p build
 	CGO_ENABLED=0 go build -o $@ -ldflags "-w -s -X github.com/topolvm/topolvm.Version=$(TOPOLVM_VERSION)" ./pkg/lvmd
 
@@ -187,6 +219,8 @@ tools: install-kind ## Install development tools.
 	GOBIN=$(BINDIR) go install github.com/norwoodj/helm-docs/cmd/helm-docs@v$(HELM_DOCS_VERSION)
 	$(CURL) https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz \
 		| tar xvz -C $(BINDIR) --strip-components 1 linux-amd64/helm
+	$(CURL) https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64 -o $(BINDIR)/yq \
+		&& chmod +x $(BINDIR)/yq
 
 .PHONY: setup
 setup: ## Setup local environment.
diff --git a/PROJECT b/PROJECT
index d9f84e88..62280299 100644
--- a/PROJECT
+++ b/PROJECT
@@ -1,4 +1,4 @@
-domain: cybozu.com
+domain: io
 layout:
 - go.kubebuilder.io/v3
 projectName: topolvm
@@ -7,7 +7,7 @@ resources:
 - api:
     crdVersion: v1
   controller: true
-  domain: cybozu.com
+  domain: io
   group: topolvm
   kind: LogicalVolume
   path: github.com/topolvm/topolvm/api/v1
diff --git a/RELEASE.md b/RELEASE.md
index 37c6eb53..35ff3361 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -63,12 +63,6 @@ Bump version
     $ sed -r -i "s/checkout v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+/checkout v${VERSION}/g" example/README.md
     ```
 4. Edit `CHANGELOG.md` for the new version ([example][]).
-   The candidate of relevant PRs can be obtained by the following command.
-   ```
-   git log --merges --format="%s%x00%b" $(git tag | grep "^v" | sort -V -r | head -n 1)..main | sed -E 's|^Merge pull request #([0-9]*)[^\x0]*\x0(.*)|- \2 ([#\1](https://github.com/topolvm/topolvm/pull/\1))|' | tac
-   ```
-   Please remove PRs which contain changes only to the helm chart.
-
 5. Commit the change and create a pull request:
 
     ```console
@@ -127,12 +121,6 @@ This will prevent the TopoLVM version from going up just by modifying the Helm C
     ```
 
 4. Edit `charts/topolvm/CHANGELOG.md` for the new version ([example][]).
-   The candidate of relevant PRs can be obtained by the following command.
-   ```
-   git log --merges --format="%s%x00%b" $(git tag | grep "^topolvm-chart-v" | sort -V -r | head -n 1)..main | sed -E 's|^Merge pull request #([0-9]*)[^\x0]*\x0(.*)|- \2 ([#\1](https://github.com/topolvm/topolvm/pull/\1))|' | tac
-   ```
-   Please select PRs which contain changes to the helm chart.
-
 5. Commit the change and create a pull request:
 
     ```console
diff --git a/api/legacy/v1/groupversion_info.go b/api/legacy/v1/groupversion_info.go
new file mode 100644
index 00000000..5108c105
--- /dev/null
+++ b/api/legacy/v1/groupversion_info.go
@@ -0,0 +1,20 @@
+// Package v1 contains API Schema definitions for the topolvm v1 API group
+//+kubebuilder:object:generate=true
+//+groupName=topolvm.cybozu.com
+package v1
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+	// GroupVersion is group version used to register these objects
+	GroupVersion = schema.GroupVersion{Group: "topolvm.cybozu.com", Version: "v1"}
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+	// AddToScheme adds the types in this group-version to the given scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+)
diff --git a/api/legacy/v1/logicalvolume_types.go b/api/legacy/v1/logicalvolume_types.go
new file mode 100644
index 00000000..a5437805
--- /dev/null
+++ b/api/legacy/v1/logicalvolume_types.go
@@ -0,0 +1,82 @@
+package v1
+
+import (
+	"google.golang.org/grpc/codes"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
+
+// LogicalVolumeSpec defines the desired state of LogicalVolume
+type LogicalVolumeSpec struct {
+	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+	// Important: Run "make" to regenerate code after modifying this file
+
+	Name        string            `json:"name"`
+	NodeName    string            `json:"nodeName"`
+	Size        resource.Quantity `json:"size"`
+	DeviceClass string            `json:"deviceClass,omitempty"`
+
+	// 'source' specifies the logicalvolume name of the source; if present.
+	// This field is populated only when LogicalVolume has a source.
+	// +kubebuilder:validation:Optional
+	Source string `json:"source,omitempty"`
+
+	//'accessType' specifies how the user intends to consume the snapshot logical volume.
+	// Set to "ro" when creating a snapshot and to "rw" when restoring a snapshot or creating a clone.
+	// This field is populated only when LogicalVolume has a source.
+	// +kubebuilder:validation:Optional
+	AccessType string `json:"accessType,omitempty"`
+}
+
+// LogicalVolumeStatus defines the observed state of LogicalVolume
+type LogicalVolumeStatus struct {
+	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+	// Important: Run "make" to regenerate code after modifying this file
+	VolumeID    string             `json:"volumeID,omitempty"`
+	Code        codes.Code         `json:"code,omitempty"`
+	Message     string             `json:"message,omitempty"`
+	CurrentSize *resource.Quantity `json:"currentSize,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+//+kubebuilder:resource:scope=Cluster
+
+// LogicalVolume is the Schema for the logicalvolumes API
+type LogicalVolume struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   LogicalVolumeSpec   `json:"spec,omitempty"`
+	Status LogicalVolumeStatus `json:"status,omitempty"`
+}
+
+// IsCompatibleWith returns true if the LogicalVolume is compatible.
+func (lv *LogicalVolume) IsCompatibleWith(lv2 *LogicalVolume) bool {
+	if lv.Spec.Name != lv2.Spec.Name {
+		return false
+	}
+	if lv.Spec.Source != lv2.Spec.Source {
+		return false
+	}
+	if lv.Spec.Size.Cmp(lv2.Spec.Size) != 0 {
+		return false
+	}
+	return true
+}
+
+//+kubebuilder:object:root=true
+
+// LogicalVolumeList contains a list of LogicalVolume
+type LogicalVolumeList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []LogicalVolume `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&LogicalVolume{}, &LogicalVolumeList{})
+}
diff --git a/api/legacy/v1/zz_generated.deepcopy.go b/api/legacy/v1/zz_generated.deepcopy.go
new file mode 100644
index 00000000..e5c2305e
--- /dev/null
+++ b/api/legacy/v1/zz_generated.deepcopy.go
@@ -0,0 +1,105 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LogicalVolume) DeepCopyInto(out *LogicalVolume) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalVolume.
+func (in *LogicalVolume) DeepCopy() *LogicalVolume {
+	if in == nil {
+		return nil
+	}
+	out := new(LogicalVolume)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *LogicalVolume) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LogicalVolumeList) DeepCopyInto(out *LogicalVolumeList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]LogicalVolume, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalVolumeList.
+func (in *LogicalVolumeList) DeepCopy() *LogicalVolumeList {
+	if in == nil {
+		return nil
+	}
+	out := new(LogicalVolumeList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *LogicalVolumeList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LogicalVolumeSpec) DeepCopyInto(out *LogicalVolumeSpec) {
+	*out = *in
+	out.Size = in.Size.DeepCopy()
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalVolumeSpec.
+func (in *LogicalVolumeSpec) DeepCopy() *LogicalVolumeSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(LogicalVolumeSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LogicalVolumeStatus) DeepCopyInto(out *LogicalVolumeStatus) {
+	*out = *in
+	if in.CurrentSize != nil {
+		in, out := &in.CurrentSize, &out.CurrentSize
+		x := (*in).DeepCopy()
+		*out = &x
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogicalVolumeStatus.
+func (in *LogicalVolumeStatus) DeepCopy() *LogicalVolumeStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(LogicalVolumeStatus)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/api/v1/groupversion_info.go b/api/v1/groupversion_info.go
index 5108c105..fe10e2a4 100644
--- a/api/v1/groupversion_info.go
+++ b/api/v1/groupversion_info.go
@@ -1,6 +1,6 @@
 // Package v1 contains API Schema definitions for the topolvm v1 API group
 //+kubebuilder:object:generate=true
-//+groupName=topolvm.cybozu.com
+//+groupName=topolvm.io
 package v1
 
 import (
@@ -10,7 +10,7 @@ import (
 
 var (
 	// GroupVersion is group version used to register these objects
-	GroupVersion = schema.GroupVersion{Group: "topolvm.cybozu.com", Version: "v1"}
+	GroupVersion = schema.GroupVersion{Group: "topolvm.io", Version: "v1"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
 	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
diff --git a/charts/topolvm/MIGRATION.md b/charts/topolvm/MIGRATION.md
index e29e6a48..6e888049 100644
--- a/charts/topolvm/MIGRATION.md
+++ b/charts/topolvm/MIGRATION.md
@@ -27,7 +27,7 @@ for example:
 | Certificate        | [webhook-ca](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/certificates.yaml#L11-L27) | `{{ template "topolvm.fullname" . }}-webhook-ca` |
 | Issuer             | [webhook-ca](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/certificates.yaml#L29-L37) | `{{ template "topolvm.fullname" . }}-webhook-ca` |
 | Certificate        | [mutatingwebhook](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/certificates.yaml#L39-L58) | `{{ template "topolvm.fullname" . }}-mutatingwebhook` |
-| CSIDriver          | [topolvm.cybozu.com](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/controller.yaml#L2-L11) | **NO CHANGED** |
+| CSIDriver          | [topolvm.io](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/controller.yaml#L2-L11) | [Please read here](./README.md#about-the-legacy-flag) |
 | ServiceAccount     | [controller](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/controller.yaml#L14-L18) | `{{ template "topolvm.fullname" . }}-controller` |
 | ClusterRole        | [topolvm-system:controller](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/controller.yaml#L20-L39) | `{{ .Release.Namespace }}:controller` |
 | ClusterRoleBinding | [topolvm-system:controller](https://github.com/topolvm/topolvm/blob/v0.8.3/deploy/manifests/base/controller.yaml#L41-L52) | `{{ .Release.Namespace }}:controller` |
diff --git a/charts/topolvm/README.md b/charts/topolvm/README.md
index 6d2d88f1..6a5e6d8d 100644
--- a/charts/topolvm/README.md
+++ b/charts/topolvm/README.md
@@ -39,8 +39,8 @@ Also, [lvmd](../../deploy/README.md#lvmd) is run in a DaemonSet by default.
 To work webhooks properly, add a label to the target namespace. We also recommend to use a dedicated namespace.
 
 ```sh
-kubectl label namespace topolvm-system topolvm.cybozu.com/webhook=ignore
-kubectl label namespace kube-system topolvm.cybozu.com/webhook=ignore
+kubectl label namespace topolvm-system topolvm.io/webhook=ignore
+kubectl label namespace kube-system topolvm.io/webhook=ignore
 ```
 
 Install the chart with the release name `topolvm` into the namespace:
@@ -180,6 +180,7 @@ You need to configure kube-scheduler to use topolvm-scheduler extender by referr
 | securityContext.runAsUser | int | `10000` | Specify runAsUser. |
 | snapshot.enabled | bool | `true` | Turn on the snapshot feature. |
 | storageClasses | list | `[{"name":"topolvm-provisioner","storageClass":{"additionalParameters":{},"allowVolumeExpansion":true,"annotations":{},"fsType":"xfs","isDefaultClass":false,"reclaimPolicy":null,"volumeBindingMode":"WaitForFirstConsumer"}}]` | Whether to create storageclass(s) ref: https://kubernetes.io/docs/concepts/storage/storage-classes/ |
+| useLegacy | bool | `false` | If true, the legacy plugin name and legacy custom resource group is used(topolvm.cybozu.com). |
 | webhook.caBundle | string | `nil` | Specify the certificate to be used for AdmissionWebhook. |
 | webhook.existingCertManagerIssuer | object | `{}` | Specify the cert-manager issuer to be used for AdmissionWebhook. |
 | webhook.podMutatingWebhook.enabled | bool | `true` | Enable Pod MutatingWebhook. |
@@ -192,6 +193,13 @@ You can use the `helm template` command to render manifests.
 helm template --include-crds --namespace=topolvm-system topolvm topolvm/topolvm
 ```
 
+## About the legacy flag
+
+In https://github.com/topolvm/topolvm/pull/592, the domain name which is used for CRD and other purposes was changed from topolvm.cybozu.com to topolvm.io.
+Automatic domain name migration to topolvm.io is risky from the data integrity point of view, and migration to topolvm.io has a large impact on the entire TopoLVM system, including CRDs.
+So we added an option to use topolvm.cybozu.com as it is.
+TopoLVM users can continue to use topolvm.cybozu.com by setting `--set useLegacy=true` in their helm chart.
+
 ## Update README
 
 The `README.md` for this chart is generated by [helm-docs](https://github.com/norwoodj/helm-docs).
diff --git a/charts/topolvm/README.md.gotmpl b/charts/topolvm/README.md.gotmpl
index c2971026..dc28fa74 100644
--- a/charts/topolvm/README.md.gotmpl
+++ b/charts/topolvm/README.md.gotmpl
@@ -39,8 +39,8 @@ Also, [lvmd](../../deploy/README.md#lvmd) is run in a DaemonSet by default.
 To work webhooks properly, add a label to the target namespace. We also recommend to use a dedicated namespace.
 
 ```sh
-kubectl label namespace topolvm-system topolvm.cybozu.com/webhook=ignore
-kubectl label namespace kube-system topolvm.cybozu.com/webhook=ignore
+kubectl label namespace topolvm-system topolvm.io/webhook=ignore
+kubectl label namespace kube-system topolvm.io/webhook=ignore
 ```
 
 Install the chart with the release name `topolvm` into the namespace:
@@ -88,6 +88,13 @@ You can use the `helm template` command to render manifests.
 helm template --include-crds --namespace=topolvm-system topolvm topolvm/topolvm
 ```
 
+## About the legacy flag
+
+In https://github.com/topolvm/topolvm/pull/592, the domain name which is used for CRD and other purposes was changed from topolvm.cybozu.com to topolvm.io.
+Automatic domain name migration to topolvm.io is risky from the data integrity point of view, and migration to topolvm.io has a large impact on the entire TopoLVM system, including CRDs.
+So we added an option to use topolvm.cybozu.com as it is.
+TopoLVM users can continue to use topolvm.cybozu.com by setting `--set useLegacy=true` in their helm chart.
+
 ## Update README
 
 The `README.md` for this chart is generated by [helm-docs](https://github.com/norwoodj/helm-docs).
diff --git a/charts/topolvm/templates/_helpers.tpl b/charts/topolvm/templates/_helpers.tpl
index 941b7bf9..2d54227d 100644
--- a/charts/topolvm/templates/_helpers.tpl
+++ b/charts/topolvm/templates/_helpers.tpl
@@ -60,3 +60,10 @@ Create the name of the service account to use
 {{- default "default" .Values.serviceAccount.name }}
 {{- end }}
 {{- end }}
+
+{{/*
+Plugin Name.
+*/}}
+{{- define "topolvm.pluginName" -}}
+{{ if .Values.useLegacy }}topolvm.cybozu.com{{ else }}topolvm.io{{ end }}
+{{- end }} 
diff --git a/charts/topolvm/templates/controller/clusterrolebindings.yaml b/charts/topolvm/templates/controller/clusterrolebindings.yaml
index d1d4c8fb..e9ab2218 100644
--- a/charts/topolvm/templates/controller/clusterrolebindings.yaml
+++ b/charts/topolvm/templates/controller/clusterrolebindings.yaml
@@ -16,7 +16,7 @@ roleRef:
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  name: topolvm-csi-provisioner-role
+  name: {{ .Release.Namespace }}-csi-provisioner-role
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
 subjects:
@@ -25,13 +25,13 @@ subjects:
     name: {{ template "topolvm.fullname" . }}-controller
 roleRef:
   kind: ClusterRole
-  name: topolvm-external-provisioner-runner
+  name: {{ .Release.Namespace }}-external-provisioner-runner
   apiGroup: rbac.authorization.k8s.io
 ---
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  name: topolvm-csi-resizer-role
+  name: {{ .Release.Namespace }}-csi-resizer-role
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
 subjects:
@@ -40,13 +40,13 @@ subjects:
     name: {{ template "topolvm.fullname" . }}-controller
 roleRef:
   kind: ClusterRole
-  name: topolvm-external-resizer-runner
+  name: {{ .Release.Namespace }}-external-resizer-runner
   apiGroup: rbac.authorization.k8s.io
 ---
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  name: topolvm-csi-snapshotter-role
+  name: {{ .Release.Namespace }}-csi-snapshotter-role
   labels:
   {{- include "topolvm.labels" . | nindent 4 }}
 subjects:
diff --git a/charts/topolvm/templates/controller/clusterroles.yaml b/charts/topolvm/templates/controller/clusterroles.yaml
index baca96fb..307e6026 100644
--- a/charts/topolvm/templates/controller/clusterroles.yaml
+++ b/charts/topolvm/templates/controller/clusterroles.yaml
@@ -17,14 +17,14 @@ rules:
   - apiGroups: ["storage.k8s.io"]
     resources: ["storageclasses","csidrivers"]
     verbs: ["get", "list", "watch"]
-  - apiGroups: ["topolvm.cybozu.com"]
+  - apiGroups: ["{{ include "topolvm.pluginName" . }}"]
     resources: ["logicalvolumes", "logicalvolumes/status"]
     verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
 ---
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  name: topolvm-external-provisioner-runner
+  name: {{ .Release.Namespace }}-external-provisioner-runner
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
 rules:
@@ -68,7 +68,7 @@ rules:
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  name: topolvm-external-resizer-runner
+  name: {{ .Release.Namespace }}-external-resizer-runner
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
 rules:
@@ -96,7 +96,7 @@ rules:
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  name: topolvm-external-snapshotter-runner
+  name: {{ .Release.Namespace }}-external-snapshotter-runner
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
 rules:
diff --git a/charts/topolvm/templates/controller/csidriver.yaml b/charts/topolvm/templates/controller/csidriver.yaml
index 5c6b5cb8..e2004a0f 100644
--- a/charts/topolvm/templates/controller/csidriver.yaml
+++ b/charts/topolvm/templates/controller/csidriver.yaml
@@ -1,7 +1,7 @@
 apiVersion: storage.k8s.io/v1
 kind: CSIDriver
 metadata:
-  name: topolvm.cybozu.com
+  name: {{ include "topolvm.pluginName" . }}
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
 spec:
diff --git a/charts/topolvm/templates/controller/deployment.yaml b/charts/topolvm/templates/controller/deployment.yaml
index 70a604da..796bafcc 100644
--- a/charts/topolvm/templates/controller/deployment.yaml
+++ b/charts/topolvm/templates/controller/deployment.yaml
@@ -42,6 +42,11 @@ spec:
             {{- if .Values.controller.nodeFinalize.skipped }}
             - --skip-node-finalize
             {{- end }}
+          {{ if .Values.useLegacy }}
+          env:
+            - name: USE_LEGACY
+              value: "true"
+          {{ end }}
           {{- with .Values.controller.args }}
           args: {{ toYaml . | nindent 12 }}
           {{- end }}
diff --git a/charts/topolvm/crds/topolvm.cybozu.com_logicalvolumes.yaml b/charts/topolvm/templates/crds/topolvm.cybozu.com_logicalvolumes.yaml
similarity index 98%
rename from charts/topolvm/crds/topolvm.cybozu.com_logicalvolumes.yaml
rename to charts/topolvm/templates/crds/topolvm.cybozu.com_logicalvolumes.yaml
index fe0d3906..f88f987a 100644
--- a/charts/topolvm/crds/topolvm.cybozu.com_logicalvolumes.yaml
+++ b/charts/topolvm/templates/crds/topolvm.cybozu.com_logicalvolumes.yaml
@@ -1,3 +1,4 @@
+{{ if .Values.useLegacy }}
 ---
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
@@ -78,3 +79,5 @@ spec:
       storage: true
       subresources:
         status: {}
+
+{{ end }}
diff --git a/charts/topolvm/templates/crds/topolvm.io_logicalvolumes.yaml b/charts/topolvm/templates/crds/topolvm.io_logicalvolumes.yaml
new file mode 100644
index 00000000..acdd46da
--- /dev/null
+++ b/charts/topolvm/templates/crds/topolvm.io_logicalvolumes.yaml
@@ -0,0 +1,83 @@
+{{ if not .Values.useLegacy }}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.0
+  creationTimestamp: null
+  name: logicalvolumes.topolvm.io
+spec:
+  group: topolvm.io
+  names:
+    kind: LogicalVolume
+    listKind: LogicalVolumeList
+    plural: logicalvolumes
+    singular: logicalvolume
+  scope: Cluster
+  versions:
+    - name: v1
+      schema:
+        openAPIV3Schema:
+          description: LogicalVolume is the Schema for the logicalvolumes API
+          properties:
+            apiVersion:
+              description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+              type: string
+            kind:
+              description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: LogicalVolumeSpec defines the desired state of LogicalVolume
+              properties:
+                accessType:
+                  description: '''accessType'' specifies how the user intends to consume the snapshot logical volume. Set to "ro" when creating a snapshot and to "rw" when restoring a snapshot or creating a clone. This field is populated only when LogicalVolume has a source.'
+                  type: string
+                deviceClass:
+                  type: string
+                name:
+                  type: string
+                nodeName:
+                  type: string
+                size:
+                  anyOf:
+                    - type: integer
+                    - type: string
+                  pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                  x-kubernetes-int-or-string: true
+                source:
+                  description: '''source'' specifies the logicalvolume name of the source; if present. This field is populated only when LogicalVolume has a source.'
+                  type: string
+              required:
+                - name
+                - nodeName
+                - size
+              type: object
+            status:
+              description: LogicalVolumeStatus defines the observed state of LogicalVolume
+              properties:
+                code:
+                  description: A Code is an unsigned 32-bit error code as defined in the gRPC spec.
+                  format: int32
+                  type: integer
+                currentSize:
+                  anyOf:
+                    - type: integer
+                    - type: string
+                  pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                  x-kubernetes-int-or-string: true
+                message:
+                  type: string
+                volumeID:
+                  description: 'INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying this file'
+                  type: string
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+
+{{ end }}
diff --git a/charts/topolvm/templates/lvmd/daemonset.yaml b/charts/topolvm/templates/lvmd/daemonset.yaml
index 61e615a1..99c1d6f1 100644
--- a/charts/topolvm/templates/lvmd/daemonset.yaml
+++ b/charts/topolvm/templates/lvmd/daemonset.yaml
@@ -50,17 +50,6 @@ spec:
           {{- with .Values.lvmd.args }}
           args: {{ toYaml . | nindent 12 }}
           {{- end }}
-          {{- if .Values.lvmd.env }}
-          env:
-          {{- toYaml .Values.lvmd.env | nindent 10 }}
-          {{- end }}
-          livenessProbe:
-            exec:
-              command:
-                - /lvmd
-                - health
-            initialDelaySeconds: 10
-            timeoutSeconds: 3
           {{- with .Values.resources.lvmd }}
           resources: {{ toYaml . | nindent 12 }}
           {{- end }}
@@ -73,6 +62,10 @@ spec:
             - name: lvmd-socket-dir
               mountPath: {{ dir .Values.lvmd.socketName }}
             {{- end }}
+          {{- if .Values.lvmd.env }}
+          env:
+          {{- toYaml .Values.lvmd.env | nindent 10 }}
+          {{- end }}
       volumes:
         - name: config
           configMap:
diff --git a/charts/topolvm/templates/mutatingwebhooks.yaml b/charts/topolvm/templates/mutatingwebhooks.yaml
index eed3480b..98874a07 100644
--- a/charts/topolvm/templates/mutatingwebhooks.yaml
+++ b/charts/topolvm/templates/mutatingwebhooks.yaml
@@ -12,13 +12,13 @@ metadata:
     {{- include "topolvm.labels" . | nindent 4 }}
 webhooks:
   {{- if .Values.webhook.podMutatingWebhook.enabled }}
-  - name: pod-hook.topolvm.cybozu.com
+  - name: pod-hook.{{ include "topolvm.pluginName" . }}
     admissionReviewVersions:
       - "v1"
       - "v1beta1"
     namespaceSelector:
       matchExpressions:
-        - key: topolvm.cybozu.com/webhook
+        - key: {{ include "topolvm.pluginName" . }}/webhook
           operator: NotIn
           values: ["ignore"]
     failurePolicy: Fail
diff --git a/charts/topolvm/templates/node/clusterrole.yaml b/charts/topolvm/templates/node/clusterrole.yaml
index d2a82b27..b62ddd1a 100644
--- a/charts/topolvm/templates/node/clusterrole.yaml
+++ b/charts/topolvm/templates/node/clusterrole.yaml
@@ -8,7 +8,7 @@ rules:
   - apiGroups: [""]
     resources: ["nodes"]
     verbs: ["get", "list", "watch", "update", "patch"]
-  - apiGroups: ["topolvm.cybozu.com"]
+  - apiGroups: ["{{ include "topolvm.pluginName" . }}"]
     resources: ["logicalvolumes", "logicalvolumes/status"]
     verbs: ["get", "list", "watch", "create", "update", "delete", "patch"]
   - apiGroups: ["storage.k8s.io"]
diff --git a/charts/topolvm/templates/node/daemonset.yaml b/charts/topolvm/templates/node/daemonset.yaml
index a7c8f7b8..c0a912c6 100644
--- a/charts/topolvm/templates/node/daemonset.yaml
+++ b/charts/topolvm/templates/node/daemonset.yaml
@@ -37,7 +37,7 @@ spec:
           {{- end }}
           command:
             - /topolvm-node
-            - --csi-socket={{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/csi-topolvm.sock
+            - --csi-socket={{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/csi-topolvm.sock
             - --lvmd-socket={{ .Values.node.lvmdSocket }}
           {{- with .Values.node.args }}
           args: {{ toYaml . | nindent 12 }}
@@ -65,12 +65,16 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: spec.nodeName
+            {{ if .Values.useLegacy }}
+            - name: USE_LEGACY
+              value: "true"
+            {{ end }}
           volumeMounts:
             {{- if .Values.node.volumeMounts.topolvmNode }}
             {{- toYaml .Values.node.volumeMounts.topolvmNode | nindent 12 }}
             {{- else }}
             - name: node-plugin-dir
-              mountPath: {{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/
+              mountPath: {{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/
             - name: lvmd-socket-dir
               mountPath: {{ dir .Values.node.lvmdSocket }}
             - name: pod-volumes-dir
@@ -92,13 +96,13 @@ spec:
           {{- end }}
           command:
             - /csi-node-driver-registrar
-            - --csi-address={{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/csi-topolvm.sock
-            - --kubelet-registration-path={{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/csi-topolvm.sock
+            - --csi-address={{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/csi-topolvm.sock
+            - --kubelet-registration-path={{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/csi-topolvm.sock
           livenessProbe:
             exec:
               command:
               - /csi-node-driver-registrar
-              - --kubelet-registration-path={{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/csi-topolvm.sock
+              - --kubelet-registration-path={{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/csi-topolvm.sock
               - --mode=kubelet-registration-probe
             initialDelaySeconds: 3
           lifecycle:
@@ -107,13 +111,13 @@ spec:
                 command:
                 - /bin/sh
                 - -c
-                - rm -rf /registration/topolvm.cybozu.com /registration/topolvm.cybozu.com-reg.sock
+                - rm -rf /registration/{{ include "topolvm.pluginName" . }} /registration/{{ include "topolvm.pluginName" . }}-reg.sock
           {{- with .Values.resources.csi_registrar }}
           resources: {{ toYaml . | nindent 12 }}
           {{- end }}
           volumeMounts:
             - name: node-plugin-dir
-              mountPath: {{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/
+              mountPath: {{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/
             - name: registration-dir
               mountPath: /registration
 
@@ -128,13 +132,13 @@ spec:
           {{- end }}
           command:
             - /livenessprobe
-            - --csi-address={{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/csi-topolvm.sock
+            - --csi-address={{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/csi-topolvm.sock
           {{- with .Values.resources.liveness_probe }}
           resources: {{ toYaml . | nindent 12 }}
           {{- end }}
           volumeMounts:
             - name: node-plugin-dir
-              mountPath: {{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node/
+              mountPath: {{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node/
 
       volumes:
         {{- if .Values.node.volumes }}
@@ -146,7 +150,7 @@ spec:
             type: Directory
         - name: node-plugin-dir
           hostPath:
-            path: {{ .Values.node.kubeletWorkDirectory }}/plugins/topolvm.cybozu.com/node
+            path: {{ .Values.node.kubeletWorkDirectory }}/plugins/{{ include "topolvm.pluginName" . }}/node
             type: DirectoryOrCreate
         - name: csi-plugin-dir
           hostPath:
diff --git a/charts/topolvm/templates/scheduler/daemonset.yaml b/charts/topolvm/templates/scheduler/daemonset.yaml
index 3dbb8961..e0535fcb 100644
--- a/charts/topolvm/templates/scheduler/daemonset.yaml
+++ b/charts/topolvm/templates/scheduler/daemonset.yaml
@@ -45,6 +45,11 @@ spec:
           command:
             - /topolvm-scheduler
             - --config=/etc/topolvm/scheduler-options.yaml
+          {{ if .Values.useLegacy }}
+          env:
+            - name: USE_LEGACY
+              value: "true"
+          {{ end }}
           {{- with .Values.scheduler.args }}
           args: {{ toYaml . | nindent 12 }}
           {{- end }}
diff --git a/charts/topolvm/templates/scheduler/deployment.yaml b/charts/topolvm/templates/scheduler/deployment.yaml
index 8db27316..a030d532 100644
--- a/charts/topolvm/templates/scheduler/deployment.yaml
+++ b/charts/topolvm/templates/scheduler/deployment.yaml
@@ -46,6 +46,11 @@ spec:
           command:
             - /topolvm-scheduler
             - --config=/etc/topolvm/scheduler-options.yaml
+          {{ if .Values.useLegacy }}
+          env:
+            - name: USE_LEGACY
+              value: "true"
+          {{ end }}
           {{- with .Values.scheduler.args }}
           args: {{ toYaml . | nindent 12 }}
           {{- end }}
diff --git a/charts/topolvm/templates/storageclass.yaml b/charts/topolvm/templates/storageclass.yaml
index d0ec90cd..7868cf66 100644
--- a/charts/topolvm/templates/storageclass.yaml
+++ b/charts/topolvm/templates/storageclass.yaml
@@ -17,7 +17,7 @@ metadata:
     {{- end }}
   labels:
     {{- include "topolvm.labels" . | nindent 4 }}
-provisioner: topolvm.cybozu.com
+provisioner: {{ include "topolvm.pluginName" . }}
 parameters:
   "csi.storage.k8s.io/fstype": {{ $val.storageClass.fsType | quote }}
   {{- with $val.storageClass.additionalParameters }}
diff --git a/charts/topolvm/values.yaml b/charts/topolvm/values.yaml
index 58def3fe..12f03dbd 100644
--- a/charts/topolvm/values.yaml
+++ b/charts/topolvm/values.yaml
@@ -1,3 +1,6 @@
+# useLegacy -- If true, the legacy plugin name and legacy custom resource group is used(topolvm.cybozu.com).
+useLegacy: false
+
 image:
   # image.repository -- TopoLVM image repository to use.
   repository: ghcr.io/topolvm/topolvm-with-sidecar
@@ -268,7 +271,7 @@ node:
   #      type: Directory
   #  - name: node-plugin-dir
   #    hostPath:
-  #      path: /var/lib/kubelet/plugins/topolvm.cybozu.com/node
+  #      path: /var/lib/kubelet/plugins/topolvm.io/node
   #      type: DirectoryOrCreate
   #  - name: csi-plugin-dir
   #    hostPath:
@@ -287,7 +290,7 @@ node:
     # node.volumeMounts.topolvmNode -- Specify volumes.
     topolvmNode: []
     # - name: node-plugin-dir
-    #   mountPath: /var/lib/kubelet/plugins/topolvm.cybozu.com/node
+    #   mountPath: /var/lib/kubelet/plugins/topolvm.io/node
     # - name: csi-plugin-dir
     #   mountPath: /var/lib/kubelet/plugins/kubernetes.io/csi
     #   mountPropagation: "Bidirectional"
@@ -517,7 +520,7 @@ storageClasses:
       # enables CSI drivers to expand volumes. This feature is available for Kubernetes 1.16 and later releases.
       allowVolumeExpansion: true
       additionalParameters: {}
-      # "topolvm.cybozu.com/device-class": "ssd"
+      # "topolvm.io/device-class": "ssd"
 
 webhook:
   # webhook.caBundle -- Specify the certificate to be used for AdmissionWebhook.
diff --git a/client/client.go b/client/client.go
new file mode 100644
index 00000000..aa40ad0b
--- /dev/null
+++ b/client/client.go
@@ -0,0 +1,389 @@
+package client
+
+import (
+	"context"
+
+	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
+	topolvmv1 "github.com/topolvm/topolvm/api/v1"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+	kind     = "LogicalVolume"
+	kindList = "LogicalVolumeList"
+)
+
+var (
+	group = topolvmv1.GroupVersion.Group
+)
+
+type wrappedReader struct {
+	client client.Reader
+	scheme *runtime.Scheme
+}
+
+var _ client.Reader = &wrappedReader{}
+
+func NewWrappedReader(c client.Reader, s *runtime.Scheme) client.Reader {
+	return &wrappedReader{
+		client: c,
+		scheme: s,
+	}
+}
+
+func (c *wrappedReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Get(ctx, key, o)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Get(ctx, key, obj)
+	case *metav1.PartialObjectMetadata:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Get(ctx, key, o)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Get(ctx, key, obj)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.scheme.Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			if err := c.client.Get(ctx, key, u); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return c.scheme.Convert(u, obj, nil)
+		}
+		return c.client.Get(ctx, key, obj)
+	}
+	return c.client.Get(ctx, key, obj)
+}
+
+func (c *wrappedReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
+	gvk := list.GetObjectKind().GroupVersionKind()
+	switch o := list.(type) {
+	case *unstructured.UnstructuredList:
+		if gvk.Group == group && gvk.Kind == kindList && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kindList))
+			err := c.client.List(ctx, list, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kindList))
+			return err
+		}
+		return c.client.List(ctx, list, opts...)
+	case *metav1.PartialObjectMetadataList:
+		if gvk.Group == group && gvk.Kind == kindList && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kindList))
+			err := c.client.List(ctx, list, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kindList))
+			return err
+		}
+		return c.client.List(ctx, list, opts...)
+	case *topolvmv1.LogicalVolumeList:
+		if topolvm.UseLegacy() {
+			l := new(topolvmlegacyv1.LogicalVolumeList)
+			if err := c.client.List(ctx, l, opts...); err != nil {
+				return err
+			}
+			u := &unstructured.UnstructuredList{}
+			if err := c.scheme.Convert(l, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kindList))
+			return c.scheme.Convert(u, list, nil)
+		}
+		return c.client.List(ctx, list, opts...)
+	}
+	return c.client.List(ctx, list, opts...)
+}
+
+type wrappedClient struct {
+	reader client.Reader
+	client client.Client
+}
+
+var _ client.Client = &wrappedClient{}
+
+func NewWrappedClient(c client.Client) client.Client {
+	return &wrappedClient{
+		reader: NewWrappedReader(c, c.Scheme()),
+		client: c,
+	}
+}
+
+func (c *wrappedClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+	return c.reader.Get(ctx, key, obj)
+}
+
+func (c *wrappedClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
+	return c.reader.List(ctx, list, opts...)
+}
+
+func (c *wrappedClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Create(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Create(ctx, obj, opts...)
+	case *metav1.PartialObjectMetadata:
+		return c.client.Create(ctx, obj, opts...)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			if err := c.client.Create(ctx, u, opts...); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return c.client.Scheme().Convert(u, obj, nil)
+		}
+		return c.client.Create(ctx, obj, opts...)
+	}
+	return c.client.Create(ctx, obj, opts...)
+}
+
+func (c *wrappedClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Delete(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Delete(ctx, obj, opts...)
+	case *metav1.PartialObjectMetadata:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Delete(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Delete(ctx, obj, opts...)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			return c.client.Delete(ctx, u, opts...)
+		}
+		return c.client.Delete(ctx, obj, opts...)
+	}
+	return c.client.Delete(ctx, obj, opts...)
+}
+
+func (c *wrappedClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Update(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Update(ctx, obj, opts...)
+	case *metav1.PartialObjectMetadata:
+		return c.client.Update(ctx, obj, opts...)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			if err := c.client.Update(ctx, u, opts...); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return c.client.Scheme().Convert(u, obj, nil)
+		}
+		return c.client.Update(ctx, obj, opts...)
+	}
+	return c.client.Update(ctx, obj, opts...)
+}
+
+// wrappedClient assumes that LogicalVolume definitions on topolvm.io and topolvm.cybozu.com are identical.
+// Since patch processes resources as Objects, even if the structs are different, if the Spec and Status are the same, there is no problem with patch processing.
+// ref: https://github.com/kubernetes-sigs/controller-runtime/blob/v0.12.1/pkg/client/patch.go#L114
+func (c *wrappedClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Patch(ctx, o, patch, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Patch(ctx, obj, patch, opts...)
+	case *metav1.PartialObjectMetadata:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Patch(ctx, o, patch, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			if err := c.client.Patch(ctx, u, patch, opts...); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return c.client.Scheme().Convert(u, obj, nil)
+		}
+		return c.client.Patch(ctx, obj, patch, opts...)
+	}
+	return c.client.Patch(ctx, obj, patch, opts...)
+}
+
+func (c *wrappedClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.DeleteAllOf(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.DeleteAllOf(ctx, obj, opts...)
+	case *metav1.PartialObjectMetadata:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.DeleteAllOf(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.DeleteAllOf(ctx, obj, opts...)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			return c.client.DeleteAllOf(ctx, u, opts...)
+		}
+		return c.client.DeleteAllOf(ctx, obj, opts...)
+	}
+	return c.client.DeleteAllOf(ctx, obj, opts...)
+}
+
+func (c *wrappedClient) Status() client.StatusWriter {
+	return &wrappedStatusWriter{client: c.client}
+}
+
+func (c *wrappedClient) Scheme() *runtime.Scheme {
+	return c.client.Scheme()
+}
+
+func (c *wrappedClient) RESTMapper() meta.RESTMapper {
+	return c.client.RESTMapper()
+}
+
+type wrappedStatusWriter struct {
+	client client.Client
+}
+
+var _ client.StatusWriter = &wrappedStatusWriter{}
+
+func (c *wrappedStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Status().Update(ctx, o, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Status().Update(ctx, obj, opts...)
+	case *metav1.PartialObjectMetadata:
+		return c.client.Status().Update(ctx, obj, opts...)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			if err := c.client.Status().Update(ctx, u, opts...); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return c.client.Scheme().Convert(u, obj, nil)
+		}
+		return c.client.Status().Update(ctx, obj, opts...)
+	}
+	return c.client.Status().Update(ctx, obj, opts...)
+}
+
+// wrappedClient assumes that LogicalVolume definitions on topolvm.io and topolvm.cybozu.com are identical.
+// Since patch processes resources as Objects, even if the structs are different, if the Spec and Status are the same, there is no problem with patch processing.
+// ref: https://github.com/kubernetes-sigs/controller-runtime/blob/v0.12.1/pkg/client/patch.go#L114
+func (c *wrappedStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	switch o := obj.(type) {
+	case *unstructured.Unstructured:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Status().Patch(ctx, o, patch, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Status().Patch(ctx, obj, patch, opts...)
+	case *metav1.PartialObjectMetadata:
+		if gvk.Group == group && gvk.Kind == kind && topolvm.UseLegacy() {
+			o.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			err := c.client.Status().Patch(ctx, o, patch, opts...)
+			o.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return err
+		}
+		return c.client.Status().Patch(ctx, obj, patch, opts...)
+	case *topolvmv1.LogicalVolume:
+		if topolvm.UseLegacy() {
+			u := &unstructured.Unstructured{}
+			if err := c.client.Scheme().Convert(obj, u, nil); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmlegacyv1.GroupVersion.WithKind(kind))
+			if err := c.client.Status().Patch(ctx, u, patch, opts...); err != nil {
+				return err
+			}
+			u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+			return c.client.Scheme().Convert(u, obj, nil)
+		}
+		return c.client.Status().Patch(ctx, obj, patch, opts...)
+	}
+	return c.client.Status().Patch(ctx, obj, patch, opts...)
+}
diff --git a/config/crd/bases/topolvm.io_logicalvolumes.yaml b/config/crd/bases/topolvm.io_logicalvolumes.yaml
new file mode 100644
index 00000000..c678259a
--- /dev/null
+++ b/config/crd/bases/topolvm.io_logicalvolumes.yaml
@@ -0,0 +1,92 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.0
+  creationTimestamp: null
+  name: logicalvolumes.topolvm.io
+spec:
+  group: topolvm.io
+  names:
+    kind: LogicalVolume
+    listKind: LogicalVolumeList
+    plural: logicalvolumes
+    singular: logicalvolume
+  scope: Cluster
+  versions:
+  - name: v1
+    schema:
+      openAPIV3Schema:
+        description: LogicalVolume is the Schema for the logicalvolumes API
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: LogicalVolumeSpec defines the desired state of LogicalVolume
+            properties:
+              accessType:
+                description: '''accessType'' specifies how the user intends to consume
+                  the snapshot logical volume. Set to "ro" when creating a snapshot
+                  and to "rw" when restoring a snapshot or creating a clone. This
+                  field is populated only when LogicalVolume has a source.'
+                type: string
+              deviceClass:
+                type: string
+              name:
+                type: string
+              nodeName:
+                type: string
+              size:
+                anyOf:
+                - type: integer
+                - type: string
+                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                x-kubernetes-int-or-string: true
+              source:
+                description: '''source'' specifies the logicalvolume name of the source;
+                  if present. This field is populated only when LogicalVolume has
+                  a source.'
+                type: string
+            required:
+            - name
+            - nodeName
+            - size
+            type: object
+          status:
+            description: LogicalVolumeStatus defines the observed state of LogicalVolume
+            properties:
+              code:
+                description: A Code is an unsigned 32-bit error code as defined in
+                  the gRPC spec.
+                format: int32
+                type: integer
+              currentSize:
+                anyOf:
+                - type: integer
+                - type: string
+                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                x-kubernetes-int-or-string: true
+              message:
+                type: string
+              volumeID:
+                description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
+                  of cluster Important: Run "make" to regenerate code after modifying
+                  this file'
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index 4c4726ec..3f0250dd 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -2,7 +2,7 @@
 # since it depends on service name and namespace that are out of this kustomize package.
 # It should be run by config/default
 resources:
-- bases/topolvm.cybozu.com_logicalvolumes.yaml
+- bases/topolvm.io_logicalvolumes.yaml
 #+kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
diff --git a/config/crd/patches/cainjection_in_logicalvolumes.yaml b/config/crd/patches/cainjection_in_logicalvolumes.yaml
index 3c4acfa0..69c54643 100644
--- a/config/crd/patches/cainjection_in_logicalvolumes.yaml
+++ b/config/crd/patches/cainjection_in_logicalvolumes.yaml
@@ -4,4 +4,4 @@ kind: CustomResourceDefinition
 metadata:
   annotations:
     cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
-  name: logicalvolumes.topolvm.cybozu.com
+  name: logicalvolumes.topolvm.io
diff --git a/config/crd/patches/webhook_in_logicalvolumes.yaml b/config/crd/patches/webhook_in_logicalvolumes.yaml
index 48af54e9..c1949825 100644
--- a/config/crd/patches/webhook_in_logicalvolumes.yaml
+++ b/config/crd/patches/webhook_in_logicalvolumes.yaml
@@ -2,7 +2,7 @@
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
-  name: logicalvolumes.topolvm.cybozu.com
+  name: logicalvolumes.topolvm.io
 spec:
   conversion:
     strategy: Webhook
diff --git a/config/manager/controller_manager_config.yaml b/config/manager/controller_manager_config.yaml
index ebd3aabd..15ce65b8 100644
--- a/config/manager/controller_manager_config.yaml
+++ b/config/manager/controller_manager_config.yaml
@@ -8,4 +8,4 @@ webhook:
   port: 9443
 leaderElection:
   leaderElect: true
-  resourceName: 846b3cc5.cybozu.com
+  resourceName: 846b3cc5.topolvm.io
diff --git a/config/rbac/logicalvolume_editor_role.yaml b/config/rbac/logicalvolume_editor_role.yaml
index 48543e85..4c798ec8 100644
--- a/config/rbac/logicalvolume_editor_role.yaml
+++ b/config/rbac/logicalvolume_editor_role.yaml
@@ -5,7 +5,7 @@ metadata:
   name: logicalvolume-editor-role
 rules:
 - apiGroups:
-  - topolvm.cybozu.com
+  - topolvm.io
   resources:
   - logicalvolumes
   verbs:
@@ -17,7 +17,7 @@ rules:
   - update
   - watch
 - apiGroups:
-  - topolvm.cybozu.com
+  - topolvm.io
   resources:
   - logicalvolumes/status
   verbs:
diff --git a/config/rbac/logicalvolume_viewer_role.yaml b/config/rbac/logicalvolume_viewer_role.yaml
index 0412c19f..97fd19ad 100644
--- a/config/rbac/logicalvolume_viewer_role.yaml
+++ b/config/rbac/logicalvolume_viewer_role.yaml
@@ -5,7 +5,7 @@ metadata:
   name: logicalvolume-viewer-role
 rules:
 - apiGroups:
-  - topolvm.cybozu.com
+  - topolvm.io
   resources:
   - logicalvolumes
   verbs:
@@ -13,7 +13,7 @@ rules:
   - list
   - watch
 - apiGroups:
-  - topolvm.cybozu.com
+  - topolvm.io
   resources:
   - logicalvolumes/status
   verbs:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index dc9a1041..3bfae1fe 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -51,7 +51,7 @@ rules:
   - list
   - watch
 - apiGroups:
-  - topolvm.cybozu.com
+  - topolvm.io
   resources:
   - logicalvolumes
   verbs:
@@ -63,7 +63,7 @@ rules:
   - update
   - watch
 - apiGroups:
-  - topolvm.cybozu.com
+  - topolvm.io
   resources:
   - logicalvolumes/status
   verbs:
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
index 450d74f9..1a845cf6 100644
--- a/config/webhook/manifests.yaml
+++ b/config/webhook/manifests.yaml
@@ -15,7 +15,7 @@ webhooks:
       path: /pod/mutate
   failurePolicy: Fail
   matchPolicy: Equivalent
-  name: pod-hook.topolvm.cybozu.com
+  name: pod-hook.topolvm.io
   rules:
   - apiGroups:
     - ""
diff --git a/constants.go b/constants.go
index 6f36254a..eebae90c 100644
--- a/constants.go
+++ b/constants.go
@@ -1,33 +1,69 @@
 package topolvm
 
-import corev1 "k8s.io/api/core/v1"
-
-// CapacityKeyPrefix is the key prefix of Node annotation that represents VG free space.
-const CapacityKeyPrefix = "capacity.topolvm.cybozu.com/"
-
-// CapacityResource is the resource name of topolvm capacity.
-const CapacityResource = corev1.ResourceName("topolvm.cybozu.com/capacity")
-
-// PluginName is the name of the CSI plugin.
-const PluginName = "topolvm.cybozu.com"
-
-// TopologyNodeKey is the key of topology that represents node name.
-const TopologyNodeKey = "topology.topolvm.cybozu.com/node"
-
-// DeviceClassKey is the key used in CSI volume create requests to specify a device-class.
-const DeviceClassKey = "topolvm.cybozu.com/device-class"
-
-// ResizeRequestedAtKey is the key of LogicalVolume that represents the timestamp of the resize request.
-const ResizeRequestedAtKey = "topolvm.cybozu.com/resize-requested-at"
-
-// LogicalVolumeFinalizer is the name of LogicalVolume finalizer
-const LogicalVolumeFinalizer = "topolvm.cybozu.com/logicalvolume"
-
-// NodeFinalizer is the name of Node finalizer of TopoLVM
-const NodeFinalizer = "topolvm.cybozu.com/node"
-
-// PVCFinalizer is the name of PVC finalizer of TopoLVM
-const PVCFinalizer = "topolvm.cybozu.com/pvc"
+import (
+	"fmt"
+	"os"
+
+	corev1 "k8s.io/api/core/v1"
+)
+
+const (
+	pluginName       = "topolvm.io"
+	legacyPluginName = "topolvm.cybozu.com"
+)
+
+func UseLegacy() bool {
+	return os.Getenv("USE_LEGACY") != ""
+}
+
+// GetPluginName returns the name of the CSI plugin.
+func GetPluginName() string {
+	if UseLegacy() {
+		return legacyPluginName
+	} else {
+		return pluginName
+	}
+}
+
+// GetCapacityKeyPrefix returns the key prefix of Node annotation that represents VG free space.
+func GetCapacityKeyPrefix() string {
+	return fmt.Sprintf("capacity.%s/", GetPluginName())
+}
+
+// GetCapacityResource returns the resource name of topolvm capacity.
+func GetCapacityResource() corev1.ResourceName {
+	return corev1.ResourceName(fmt.Sprintf("%s/capacity", GetPluginName()))
+}
+
+// TopologyNodeKey returns the key of topology that represents node name.
+func GetTopologyNodeKey() string {
+	return fmt.Sprintf("topology.%s/node", GetPluginName())
+}
+
+// GetDeviceClassKey returns the key used in CSI volume create requests to specify a device-class.
+func GetDeviceClassKey() string {
+	return fmt.Sprintf("%s/device-class", GetPluginName())
+}
+
+// GetResizeRequestedAtKey returns the key of LogicalVolume that represents the timestamp of the resize request.
+func GetResizeRequestedAtKey() string {
+	return fmt.Sprintf("%s/resize-requested-at", GetPluginName())
+}
+
+// GetLogicalVolumeFinalizer returns the name of LogicalVolume finalizer
+func GetLogicalVolumeFinalizer() string {
+	return fmt.Sprintf("%s/logicalvolume", GetPluginName())
+}
+
+// GetNodeFinalizer returns the name of Node finalizer of TopoLVM
+func GetNodeFinalizer() string {
+	return fmt.Sprintf("%s/node", GetPluginName())
+}
+
+// GetPVCFinalizer returns the name of PVC finalizer of TopoLVM
+func GetPVCFinalizer() string {
+	return fmt.Sprintf("%s/pvc", GetPluginName())
+}
 
 // DefaultCSISocket is the default path of the CSI socket file.
 const DefaultCSISocket = "/run/topolvm/csi-topolvm.sock"
@@ -45,7 +81,7 @@ const DefaultDeviceClassName = ""
 const DefaultSizeGb = 1
 
 // DefaultSize is DefaultSizeGb in bytes
-const DefaultSize = DefaultSizeGb << 30
+const DefaultSize = int64(DefaultSizeGb << 30)
 
 // Label key that indicates The controller/user who created this resource
 // https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/#labels
diff --git a/controllers/logicalvolume_controller.go b/controllers/logicalvolume_controller.go
index f8470975..a97fa3b1 100644
--- a/controllers/logicalvolume_controller.go
+++ b/controllers/logicalvolume_controller.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/go-logr/logr"
 	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
 	topolvmv1 "github.com/topolvm/topolvm/api/v1"
 	"github.com/topolvm/topolvm/lvmd/proto"
 	"google.golang.org/grpc"
@@ -22,19 +23,19 @@ import (
 
 // LogicalVolumeReconciler reconciles a LogicalVolume object
 type LogicalVolumeReconciler struct {
-	client.Client
+	client    client.Client
 	nodeName  string
 	vgService proto.VGServiceClient
 	lvService proto.LVServiceClient
 }
 
-//+kubebuilder:rbac:groups=topolvm.cybozu.com,resources=logicalvolumes,verbs=get;list;watch;update;patch
-//+kubebuilder:rbac:groups=topolvm.cybozu.com,resources=logicalvolumes/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=topolvm.io,resources=logicalvolumes,verbs=get;list;watch;update;patch
+//+kubebuilder:rbac:groups=topolvm.io,resources=logicalvolumes/status,verbs=get;update;patch
 
 // NewLogicalVolumeReconciler returns LogicalVolumeReconciler with creating lvService and vgService.
 func NewLogicalVolumeReconciler(client client.Client, nodeName string, conn *grpc.ClientConn) *LogicalVolumeReconciler {
 	return &LogicalVolumeReconciler{
-		Client:    client,
+		client:    client,
 		nodeName:  nodeName,
 		vgService: proto.NewVGServiceClient(conn),
 		lvService: proto.NewLVServiceClient(conn),
@@ -46,25 +47,24 @@ func (r *LogicalVolumeReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	log := crlog.FromContext(ctx)
 
 	lv := new(topolvmv1.LogicalVolume)
-	if err := r.Get(ctx, req.NamespacedName, lv); err != nil {
+	if err := r.client.Get(ctx, req.NamespacedName, lv); err != nil {
 		if !apierrs.IsNotFound(err) {
 			log.Error(err, "unable to fetch LogicalVolume")
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
-
 	if lv.Spec.NodeName != r.nodeName {
 		log.Info("unfiltered logical value", "nodeName", lv.Spec.NodeName)
 		return ctrl.Result{}, nil
 	}
 
 	if lv.ObjectMeta.DeletionTimestamp == nil {
-		if !containsString(lv.Finalizers, topolvm.LogicalVolumeFinalizer) {
+		if !containsString(lv.Finalizers, topolvm.GetLogicalVolumeFinalizer()) {
 			lv2 := lv.DeepCopy()
-			lv2.Finalizers = append(lv2.Finalizers, topolvm.LogicalVolumeFinalizer)
+			lv2.Finalizers = append(lv2.Finalizers, topolvm.GetLogicalVolumeFinalizer())
 			patch := client.MergeFrom(lv)
-			if err := r.Patch(ctx, lv2, patch); err != nil {
+			if err := r.client.Patch(ctx, lv2, patch); err != nil {
 				log.Error(err, "failed to add finalizer", "name", lv.Name)
 				return ctrl.Result{}, err
 			}
@@ -78,7 +78,7 @@ func (r *LogicalVolumeReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 			}
 			lv2.Labels[topolvm.CreatedbyLabelKey] = topolvm.CreatedbyLabelValue
 			patch := client.MergeFrom(lv)
-			if err := r.Patch(ctx, lv2, patch); err != nil {
+			if err := r.client.Patch(ctx, lv2, patch); err != nil {
 				log.Error(err, "failed to add label", "name", lv.Name)
 				return ctrl.Result{}, err
 			}
@@ -101,7 +101,7 @@ func (r *LogicalVolumeReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	}
 
 	// finalization
-	if !containsString(lv.Finalizers, topolvm.LogicalVolumeFinalizer) {
+	if !containsString(lv.Finalizers, topolvm.GetLogicalVolumeFinalizer()) {
 		// Our finalizer has finished, so the reconciler can do nothing.
 		return ctrl.Result{}, nil
 	}
@@ -113,9 +113,9 @@ func (r *LogicalVolumeReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	}
 
 	lv2 := lv.DeepCopy()
-	lv2.Finalizers = removeString(lv2.Finalizers, topolvm.LogicalVolumeFinalizer)
+	lv2.Finalizers = removeString(lv2.Finalizers, topolvm.GetLogicalVolumeFinalizer())
 	patch := client.MergeFrom(lv)
-	if err := r.Patch(ctx, lv2, patch); err != nil {
+	if err := r.client.Patch(ctx, lv2, patch); err != nil {
 		log.Error(err, "failed to remove finalizer", "name", lv.Name)
 		return ctrl.Result{}, err
 	}
@@ -124,10 +124,13 @@ func (r *LogicalVolumeReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 
 // SetupWithManager sets up the controller with the Manager.
 func (r *LogicalVolumeReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	return ctrl.NewControllerManagedBy(mgr).
-		For(&topolvmv1.LogicalVolume{}).
-		WithEventFilter(&logicalVolumeFilter{r.nodeName}).
-		Complete(r)
+	builder := ctrl.NewControllerManagedBy(mgr)
+	if topolvm.UseLegacy() {
+		builder = builder.For(&topolvmlegacyv1.LogicalVolume{})
+	} else {
+		builder = builder.For(&topolvmv1.LogicalVolume{})
+	}
+	return builder.WithEventFilter(&logicalVolumeFilter{r.nodeName}).Complete(r)
 }
 
 func (r *LogicalVolumeReconciler) removeLVIfExists(ctx context.Context, log logr.Logger, lv *topolvmv1.LogicalVolume) error {
@@ -205,7 +208,7 @@ func (r *LogicalVolumeReconciler) createLV(ctx context.Context, log logr.Logger,
 				return fmt.Errorf("invalid access type for source volume: %s", lv.Spec.AccessType)
 			}
 			sourcelv := new(topolvmv1.LogicalVolume)
-			if err := r.Get(ctx, types.NamespacedName{Namespace: lv.Namespace, Name: lv.Spec.Source}, sourcelv); err != nil {
+			if err := r.client.Get(ctx, types.NamespacedName{Namespace: lv.Namespace, Name: lv.Spec.Source}, sourcelv); err != nil {
 				log.Error(err, "unable to fetch source LogicalVolume", "name", lv.Name)
 				return err
 			}
@@ -252,14 +255,14 @@ func (r *LogicalVolumeReconciler) createLV(ctx context.Context, log logr.Logger,
 	}()
 
 	if err != nil {
-		if err2 := r.Status().Update(ctx, lv); err2 != nil {
+		if err2 := r.client.Status().Update(ctx, lv); err2 != nil {
 			// err2 is logged but not returned because err is more important
 			log.Error(err2, "failed to update status", "name", lv.Name, "uid", lv.UID)
 		}
 		return err
 	}
 
-	if err := r.Status().Update(ctx, lv); err != nil {
+	if err := r.client.Status().Update(ctx, lv); err != nil {
 		log.Error(err, "failed to update status", "name", lv.Name, "uid", lv.UID)
 		return err
 	}
@@ -300,14 +303,14 @@ func (r *LogicalVolumeReconciler) expandLV(ctx context.Context, log logr.Logger,
 	}()
 
 	if err != nil {
-		if err2 := r.Status().Update(ctx, lv); err2 != nil {
+		if err2 := r.client.Status().Update(ctx, lv); err2 != nil {
 			// err2 is logged but not returned because err is more important
 			log.Error(err2, "failed to update status", "name", lv.Name, "uid", lv.UID)
 		}
 		return err
 	}
 
-	if err := r.Status().Update(ctx, lv); err != nil {
+	if err := r.client.Status().Update(ctx, lv); err != nil {
 		log.Error(err, "failed to update status", "name", lv.Name, "uid", lv.UID)
 		return err
 	}
@@ -321,30 +324,41 @@ type logicalVolumeFilter struct {
 	nodeName string
 }
 
-func (f logicalVolumeFilter) filter(lv *topolvmv1.LogicalVolume) bool {
-	if lv == nil {
-		return false
+func (f logicalVolumeFilter) filter(obj client.Object) bool {
+	var name string
+	if topolvm.UseLegacy() {
+		lv, ok := obj.(*topolvmlegacyv1.LogicalVolume)
+		if !ok {
+			return false
+		}
+		name = lv.Spec.NodeName
+	} else {
+		lv, ok := obj.(*topolvmv1.LogicalVolume)
+		if !ok {
+			return false
+		}
+		name = lv.Spec.NodeName
 	}
-	if lv.Spec.NodeName == f.nodeName {
+	if name == f.nodeName {
 		return true
 	}
 	return false
 }
 
 func (f logicalVolumeFilter) Create(e event.CreateEvent) bool {
-	return f.filter(e.Object.(*topolvmv1.LogicalVolume))
+	return f.filter(e.Object)
 }
 
 func (f logicalVolumeFilter) Delete(e event.DeleteEvent) bool {
-	return f.filter(e.Object.(*topolvmv1.LogicalVolume))
+	return f.filter(e.Object)
 }
 
 func (f logicalVolumeFilter) Update(e event.UpdateEvent) bool {
-	return f.filter(e.ObjectNew.(*topolvmv1.LogicalVolume))
+	return f.filter(e.ObjectNew)
 }
 
 func (f logicalVolumeFilter) Generic(e event.GenericEvent) bool {
-	return f.filter(e.Object.(*topolvmv1.LogicalVolume))
+	return f.filter(e.Object)
 }
 
 func containsString(slice []string, s string) bool {
diff --git a/controllers/node_controller.go b/controllers/node_controller.go
index 3f117d01..6f543d7e 100644
--- a/controllers/node_controller.go
+++ b/controllers/node_controller.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/go-logr/logr"
 	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
 	topolvmv1 "github.com/topolvm/topolvm/api/v1"
 	corev1 "k8s.io/api/core/v1"
 	storagev1 "k8s.io/api/storage/v1"
@@ -18,8 +19,16 @@ import (
 
 // NodeReconciler reconciles a Node object
 type NodeReconciler struct {
-	client.Client
-	SkipNodeFinalize bool
+	client           client.Client
+	skipNodeFinalize bool
+}
+
+// NewNodeReconciler returns NodeReconciler.
+func NewNodeReconciler(client client.Client, skipNodeFinalize bool) *NodeReconciler {
+	return &NodeReconciler{
+		client:           client,
+		skipNodeFinalize: skipNodeFinalize,
+	}
 }
 
 //+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch;update;patch
@@ -32,7 +41,7 @@ func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
 
 	// your logic here
 	node := &corev1.Node{}
-	err := r.Get(ctx, req.NamespacedName, node)
+	err := r.client.Get(ctx, req.NamespacedName, node)
 	switch {
 	case err == nil:
 	case apierrors.IsNotFound(err):
@@ -47,7 +56,7 @@ func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
 
 	needFinalize := false
 	for _, fin := range node.Finalizers {
-		if fin == topolvm.NodeFinalizer {
+		if fin == topolvm.GetNodeFinalizer() {
 			needFinalize = true
 			break
 		}
@@ -63,7 +72,7 @@ func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
 	node2 := node.DeepCopy()
 	finalizers := node2.Finalizers[:0]
 	for _, fin := range node.Finalizers {
-		if fin == topolvm.NodeFinalizer {
+		if fin == topolvm.GetNodeFinalizer() {
 			continue
 		}
 		finalizers = append(finalizers, fin)
@@ -71,7 +80,7 @@ func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
 	node2.Finalizers = finalizers
 
 	patch := client.MergeFrom(node)
-	if err := r.Patch(ctx, node2, patch); err != nil {
+	if err := r.client.Patch(ctx, node2, patch); err != nil {
 		log.Error(err, "failed to remove finalizer", "name", node.Name)
 		return ctrl.Result{}, err
 	}
@@ -81,13 +90,13 @@ func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
 
 func (r *NodeReconciler) targetStorageClasses(ctx context.Context) (map[string]bool, error) {
 	var scl storagev1.StorageClassList
-	if err := r.List(ctx, &scl); err != nil {
+	if err := r.client.List(ctx, &scl); err != nil {
 		return nil, err
 	}
 
 	targets := make(map[string]bool)
 	for _, sc := range scl.Items {
-		if sc.Provisioner != topolvm.PluginName {
+		if sc.Provisioner != topolvm.GetPluginName() {
 			continue
 		}
 		targets[sc.Name] = true
@@ -96,7 +105,7 @@ func (r *NodeReconciler) targetStorageClasses(ctx context.Context) (map[string]b
 }
 
 func (r *NodeReconciler) doFinalize(ctx context.Context, log logr.Logger, node *corev1.Node) (ctrl.Result, error) {
-	if r.SkipNodeFinalize {
+	if r.skipNodeFinalize {
 		log.Info("skipping node finalize")
 		return ctrl.Result{}, nil
 	}
@@ -108,7 +117,7 @@ func (r *NodeReconciler) doFinalize(ctx context.Context, log logr.Logger, node *
 	}
 
 	var pvcs corev1.PersistentVolumeClaimList
-	err = r.List(ctx, &pvcs, client.MatchingFields{keySelectedNode: node.Name})
+	err = r.client.List(ctx, &pvcs, client.MatchingFields{keySelectedNode: node.Name})
 	if err != nil {
 		log.Error(err, "unable to fetch PersistentVolumeClaimList")
 		return ctrl.Result{}, err
@@ -122,7 +131,7 @@ func (r *NodeReconciler) doFinalize(ctx context.Context, log logr.Logger, node *
 			continue
 		}
 
-		err = r.Delete(ctx, &pvc)
+		err = r.client.Delete(ctx, &pvc)
 		if err != nil {
 			log.Error(err, "unable to delete PVC", "name", pvc.Name, "namespace", pvc.Namespace)
 			return ctrl.Result{}, err
@@ -131,12 +140,11 @@ func (r *NodeReconciler) doFinalize(ctx context.Context, log logr.Logger, node *
 	}
 
 	lvList := new(topolvmv1.LogicalVolumeList)
-	err = r.List(ctx, lvList, client.MatchingFields{keyLogicalVolumeNode: node.Name})
+	err = r.client.List(ctx, lvList, client.MatchingFields{keyLogicalVolumeNode: node.Name})
 	if err != nil {
 		log.Error(err, "failed to get LogicalVolumes")
 		return ctrl.Result{}, err
 	}
-
 	for _, lv := range lvList.Items {
 		err = r.cleanupLogicalVolume(ctx, log, &lv)
 		if err != nil {
@@ -150,7 +158,7 @@ func (r *NodeReconciler) doFinalize(ctx context.Context, log logr.Logger, node *
 func (r *NodeReconciler) cleanupLogicalVolume(ctx context.Context, log logr.Logger, lv *topolvmv1.LogicalVolume) error {
 	finExists := false
 	for _, fin := range lv.Finalizers {
-		if fin == topolvm.LogicalVolumeFinalizer {
+		if fin == topolvm.GetLogicalVolumeFinalizer() {
 			finExists = true
 			break
 		}
@@ -160,26 +168,23 @@ func (r *NodeReconciler) cleanupLogicalVolume(ctx context.Context, log logr.Logg
 		lv2 := lv.DeepCopy()
 		var finalizers []string
 		for _, fin := range lv2.Finalizers {
-			if fin == topolvm.LogicalVolumeFinalizer {
+			if fin == topolvm.GetLogicalVolumeFinalizer() {
 				continue
 			}
 			finalizers = append(finalizers, fin)
 		}
 		lv2.Finalizers = finalizers
-
 		patch := client.MergeFrom(lv)
-		if err := r.Patch(ctx, lv2, patch); err != nil {
+		if err := r.client.Patch(ctx, lv2, patch); err != nil {
 			log.Error(err, "failed to patch LogicalVolume", "name", lv.Name)
 			return err
 		}
 	}
 
-	err := r.Delete(ctx, lv)
-	if err != nil {
+	if err := r.client.Delete(ctx, lv); err != nil {
 		log.Error(err, "failed to delete LogicalVolume", "name", lv.Name)
 		return err
 	}
-
 	log.Info("deleted LogicalVolume", "name", lv.Name)
 	return nil
 }
@@ -194,11 +199,20 @@ func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		return err
 	}
 
-	err = mgr.GetFieldIndexer().IndexField(ctx, &topolvmv1.LogicalVolume{}, keyLogicalVolumeNode, func(o client.Object) []string {
-		return []string{o.(*topolvmv1.LogicalVolume).Spec.NodeName}
-	})
-	if err != nil {
-		return err
+	if topolvm.UseLegacy() {
+		err = mgr.GetFieldIndexer().IndexField(ctx, &topolvmlegacyv1.LogicalVolume{}, keyLogicalVolumeNode, func(o client.Object) []string {
+			return []string{o.(*topolvmlegacyv1.LogicalVolume).Spec.NodeName}
+		})
+		if err != nil {
+			return err
+		}
+	} else {
+		err = mgr.GetFieldIndexer().IndexField(ctx, &topolvmv1.LogicalVolume{}, keyLogicalVolumeNode, func(o client.Object) []string {
+			return []string{o.(*topolvmv1.LogicalVolume).Spec.NodeName}
+		})
+		if err != nil {
+			return err
+		}
 	}
 
 	pred := predicate.Funcs{
diff --git a/controllers/persistentvolumeclaim_controller.go b/controllers/persistentvolumeclaim_controller.go
index 01e32a2d..d59cc7dc 100644
--- a/controllers/persistentvolumeclaim_controller.go
+++ b/controllers/persistentvolumeclaim_controller.go
@@ -15,7 +15,12 @@ import (
 
 // PersistentVolumeClaimReconciler reconciles a PersistentVolumeClaim object
 type PersistentVolumeClaimReconciler struct {
-	client.Client
+	client client.Client
+}
+
+// NewPersistentVolumeClaimReconciler returns NodeReconciler.
+func NewPersistentVolumeClaimReconciler(client client.Client) *PersistentVolumeClaimReconciler {
+	return &PersistentVolumeClaimReconciler{client: client}
 }
 
 //+kubebuilder:rbac:groups=core,resources=persistentvolumeclaims,verbs=get;list;watch;update
@@ -26,7 +31,7 @@ func (r *PersistentVolumeClaimReconciler) Reconcile(ctx context.Context, req ctr
 	log := crlog.FromContext(ctx)
 	// your logic here
 	pvc := &corev1.PersistentVolumeClaim{}
-	err := r.Get(ctx, req.NamespacedName, pvc)
+	err := r.client.Get(ctx, req.NamespacedName, pvc)
 	switch {
 	case err == nil:
 	case apierrors.IsNotFound(err):
@@ -63,7 +68,7 @@ func (r *PersistentVolumeClaimReconciler) removeTopoLVMFinalizer(ctx context.Con
 	// Due to the bug #310, multiple TopoLVM finalizers can exist in `pvc.Finalizers`.
 	// So we need to delete all of them.
 	for i := 0; i < len(pvc.Finalizers); {
-		if pvc.Finalizers[i] == topolvm.PVCFinalizer {
+		if pvc.Finalizers[i] == topolvm.GetPVCFinalizer() {
 			pvc.Finalizers = append(pvc.Finalizers[:i], pvc.Finalizers[i+1:]...)
 			removed = true
 			continue
@@ -71,7 +76,7 @@ func (r *PersistentVolumeClaimReconciler) removeTopoLVMFinalizer(ctx context.Con
 		i++
 	}
 	if removed {
-		if err := r.Update(ctx, pvc); err != nil {
+		if err := r.client.Update(ctx, pvc); err != nil {
 			return err
 		}
 	}
diff --git a/deploy/README.md b/deploy/README.md
index f6916996..cb5992b6 100644
--- a/deploy/README.md
+++ b/deploy/README.md
@@ -235,7 +235,7 @@ extenders:
     nodeCacheCapable: false
     weight: 100 ## EDIT THIS FIELD ##
     managedResources:
-      - name: "topolvm.cybozu.com/capacity"
+      - name: "topolvm.io/capacity"
         ignoredByScheduler: true
 ```
 
@@ -275,7 +275,7 @@ if the webhook pods and the system pods are both missing.
 To workaround the problem, add a label to system namespaces such as `kube-system` as follows:
 
 ```console
-$ kubectl label namespace kube-system topolvm.cybozu.com/webhook=ignore
+$ kubectl label namespace kube-system topolvm.io/webhook=ignore
 ```
 
 ## Configure StorageClasses
@@ -302,7 +302,7 @@ The first step is to create a namespace and add a label.
 
 ```console
 $ kubectl create namespace topolvm-system
-$ kubectl label namespace topolvm-system topolvm.cybozu.com/webhook=ignore
+$ kubectl label namespace topolvm-system topolvm.io/webhook=ignore
 ```
 
 > :memo: Helm does not support adding labels or other metadata when creating namespaces.
@@ -392,7 +392,7 @@ apiVersion: snapshot.storage.k8s.io/v1
 kind: VolumeSnapshotClass
 metadata:
   name: csi-topolvm-snapclass
-driver: topolvm.cybozu.com
+driver: topolvm.io
 deletionPolicy: Delete
 ```
 
diff --git a/deploy/scheduler-config/scheduler-config-v1beta2.yaml b/deploy/scheduler-config/scheduler-config-legacy.yaml
similarity index 100%
rename from deploy/scheduler-config/scheduler-config-v1beta2.yaml
rename to deploy/scheduler-config/scheduler-config-legacy.yaml
diff --git a/deploy/scheduler-config/scheduler-config.yaml b/deploy/scheduler-config/scheduler-config.yaml
new file mode 100644
index 00000000..0764d7bd
--- /dev/null
+++ b/deploy/scheduler-config/scheduler-config.yaml
@@ -0,0 +1,15 @@
+apiVersion: kubescheduler.config.k8s.io/v1beta2
+kind: KubeSchedulerConfiguration
+leaderElection:
+  leaderElect: true
+clientConnection:
+  kubeconfig: /etc/kubernetes/scheduler.conf
+extenders:
+- urlPrefix: "http://127.0.0.1:9251"
+  filterVerb: "predicate"
+  prioritizeVerb: "prioritize"
+  nodeCacheCapable: false
+  weight: 1
+  managedResources:
+  - name: "topolvm.io/capacity"
+    ignoredByScheduler: true
diff --git a/docs/crd-logical-volume.md b/docs/crd-logical-volume.md
index fded7015..76ed0c2c 100644
--- a/docs/crd-logical-volume.md
+++ b/docs/crd-logical-volume.md
@@ -44,7 +44,7 @@ when the volume size of the corresponding PVC is increased.
 `topolvm-node` watches the `LogicalVolume` resource and resizes the LVM logical
 volume when it finds the difference between `status.currentSize` and `spec.size`.
 In order for `topolvm-node` to retry resizing, `topolvm-controller` updates
-`metadata.annotations["topolvm.cybozu.com/resize-requested-at"]` of `LogicalVolume`.
+`metadata.annotations["topolvm.io/resize-requested-at"]` of `LogicalVolume`.
 
 After the LVM logical volume is expanded successfully, `topolvm-node` updates
 `status.currentSize` value.
diff --git a/docs/design.md b/docs/design.md
index 401cd5d3..1af6bef6 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -72,11 +72,11 @@ standard Kubernetes scheduler for TopoLVM.
 
 To extend the standard scheduler, TopoLVM components work together as follows:
 
-- `topolvm-node` exposes free storage capacity as `capacity.topolvm.cybozu.com/<device-class>` annotation of each Node.
+- `topolvm-node` exposes free storage capacity as `capacity.topolvm.io/<device-class>` annotation of each Node.
 - `topolvm-controller` works as a [mutating webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) for new Pods.
-    - It adds `capacity.topolvm.cybozu.com/<device-class>` annotation to a pod and `topolvm.cybozu.com/capacity` resource to the first container of a pod.
+    - It adds `capacity.topolvm.io/<device-class>` annotation to a pod and `topolvm.io/capacity` resource to the first container of a pod.
     - The value of the annotation is the sum of the storage capacity requests of unbound TopoLVM PVCs for each volume group referenced by the pod.
-- `topolvm-scheduler` filters and scores Nodes for a new pod having `topolvm.cybozu.com/capacity` resource request.
+- `topolvm-scheduler` filters and scores Nodes for a new pod having `topolvm.io/capacity` resource request.
     - Nodes having less capacity in given volume group than requested are filtered.
     - Nodes having larger capacity in given volume group are scored higher.
 
diff --git a/docs/proposals/rename-group.md b/docs/proposals/rename-group.md
new file mode 100644
index 00000000..99615be2
--- /dev/null
+++ b/docs/proposals/rename-group.md
@@ -0,0 +1,168 @@
+# Allocation strategies
+
+## Summary
+
+Change `topolvm.cybozu.com` used in the CRD and plugin name of TopoLVM to `topolvm.io`.
+
+## Motivation
+
+We are thinking of donating the TopoLVM project to CNCF in the future.
+So we would like to remove the domain name "cybozu.com," which contains the specific company's name, from the project.
+
+### Goals
+
+- Change `topolvm.cybozu.com` used in TopoLVM's CRD and plugin name to `topolvm.io`
+- Keep TopoLVM available at `topolvm.cybozu.com` for existing users (However, please avoid concurrently using it with `topolvm.io`)
+
+## Proposal
+
+Rename the group name `topolvm.cybozu.com` used below to `topolvm.io`:
+
+- CSI Driver plugin name
+- Various annotations and finalizers set by TopoLVM
+- Various resource settings including CSIDriver resource
+- CRD name
+
+Also, by setting the variable of helm chart, it can be used as it is at `topolvm.cybozu.com` as before.
+In addition, we will guide you through the migration procedure for users who wish to migrate their group names.
+
+## Design details
+
+### Changes to topolvm.io
+
+Change the group name `topolvm.cybozu.com` used below to `topolvm.io`.
+
+#### constants.go
+
+constant name | value
+--- | ---
+CapacityKeyPrefix | capacity.topolvm.cybozu.com/
+CapacityResource | topolvm.cybozu.com/capacity
+PluginName | topolvm.cybozu.com
+TopologyNodeKey | topology.topolvm.cybozu.com/node
+DeviceClassKey | topolvm.cybozu.com/device-class
+ResizeRequestedAtKey | topolvm.cybozu.com/resize-requested-at
+LogicalVolumeFinalizer | topolvm.cybozu.com/logicalvolume
+NodeFinalizer | topolvm.cybozu.com/node
+
+#### CRD
+
+Change from `logicalvolumes.topolvm.cybozu.com` to` logicalvolumes.topolvm.io`.
+
+#### Resources
+
+##### CSIDriver
+
+Rename the `topolvm.cybozu.com` CSIDriver resource to `topolvm.io`
+
+##### StorageClass
+
+Modify each StorageClass resource in TopoLVM as follows:
+
+- Change the value of the `provisioner` field from `topolvm.cybozu.com` to `topolvm.io`
+- Rename the key name `topolvm.cybozu.com/device-class` in the` parameters` field to `topolvm.io/device-class`
+
+**Since it is not possible to change the StorageClass resource, it is necessary to recreate the resource when migrating the group name.**
+
+##### LogicalVolume
+
+Modify each LogicalVolume resource in TopoLVM as follows:
+
+- Change the CRD to use from `logicalvolumes.topolvm.cybozu.com` to` logicalvolumes.topolvm.io`
+- Change the `topolvm.cybozu.com/resize-requested-at` annotation to` topolvm.io/resize-requested-at`
+- Change the `topolvm.cybozu.com/logicalvolume` finalizer to` topolvm.io/logicalvolume`
+
+**Since the CRD itself changes for the Logical Volume resource, it is necessary to delete the old CRD resource and create a new CRD resource when migrating the group name. Also, in order to prevent the deletion of the actual LVM volume due to the logical volume migration, it is recommended to migrate with the TopoLVM components stopped.**
+
+##### Node
+
+Make the following changes for each Node resources
+
+- Change the `topolvm.cybozu.com/node` finalizer to` topolvm.io/node`
+
+##### PersistentVolumeClaim
+
+Modify each PersistentVolumeClaim resources in TopoLVM as follows:
+
+- Change the value of the `volume.beta.kubernetes.io/storage-provisioner`[^storage-provisioner] annotation to` topolvm.io`
+- Change the value of the `volume.kubernetes.io/storage-provisioner`[^storage-provisioner] annotation to` topolvm.io`
+
+[^storage-provisioner]: https://github.com/kubernetes/kubernetes/blob/v1.24.2/pkg/controller/volume/persistentvolume/pv_controller_base.go#L638-L639
+
+##### PersistentVolume
+
+Modify each PersistentVolume resource in TopoLVM as follows (You need to recreate PersistentVolumes because it is not editable):
+
+- Change the value of the `pv.kubernetes.io/provisioned-by`[^provisioned-by] annotation to` topolvm.io`
+- Change the value of the `.spec.csi.driver` field to` topolvm.io`
+- Change the string `topolvm.cybozu.com` in the value of the `.spec.csi.volumeAttributes ["storage.kubernetes.io/csiProvisionerIdentity "]` field to` topolvm.io`
+
+[^provisioned-by]: https://github.com/kubernetes/kubernetes/blob/v1.24.2/pkg/controller/volume/persistentvolume/pv_controller.go#L1662
+
+**Since it is not possible to change the above fields of the PersistentVolume resource, it is necessary to recreate the resource when migrating the group name. Also, if `.spec.persistentVolumeReclaimPolicy = Delete` is set, there is a risk of deleting the actual LVM volume, so it is recommended to temporarily change it to `Retain` and then delete it.**
+
+### Enable the use of topolvm.cybozu.com
+
+We will make the following changes to allow users to continue to use `topolvm.cybozu.com`.
+
+#### Addition of group name setting function
+
+- If you set the `USE_LEGACY` environment variable, the group name `topolvm.cybozu.com` will be used
+- If the `USE_LEGACY` environment variable is not set, the group name `topolvm.io` will be used
+
+#### Group name setting in helm chart
+
+Add the `.Values.useLegacy` variable to the helm chart and set this variable to `true` to set the installation manifest to use `topolvm.cybozu.com`.
+If the `.Values.useLegacy` variable is not set to `true`, the group name will be `topolvm.io`.
+
+If you are going to set the following variables, you may need to set them appropriately to use `topolvm.io` or `topolvm.cybozu.com` in the variables.
+
+- `.Values.storageClasses`
+- `.Values.node.volumes`
+- `.Values.volumeMounts.topolvmNode`
+
+#### Automatic generation for API and CRD
+
+Automatically generating the API and CRD of `topolvm.cybozu.com` based on the API and CRD of `topolvm.io`.
+
+### Auto group conversion for LogicalVolume
+
+Add a wrap client for controller-runtime client to automatically convert a group of LogicalVolume resources.
+Change each controller and other components to use this wrapped client.
+
+## User action
+
+Migration to `topolvm.io` is not mandatory and `topolvm.cybozu.com` is going to be maintained. Those who still want to use `topolvm.cybozu.com` must set `.Values.useLegacy:true` for the helm chart when updating TopoLVM.
+
+### Things to be done by users of topolvm.cybozu.com for upgrading the helm chart
+
+Since this change was released, users of TopoLVM with the group name `topolvm.cybozu.com` will need to take the following actions when upgrading the helm chart.
+
+- Set the `.Values.useLegacy` variable to `true` in `values.yaml` in the helm chart
+- If there is a part of the `values.yaml` in the helm chart that uses the string `topolvm.cybozu.com`, change it to `topolvm.io`
+
+If you upgrade the helm chart without taking above actions, it is possible that TopoLVM related resources will be deleted.
+
+### Migrate from topolvm.cybozu.com to topolvm.io
+
+If you are already using TopoLVM with the group name topolvm.cybozu.com and want to migrate the group name to topolvm.io after the release of this change, you can manually migrate the data by the following method.
+
+**You should back up LVM data managed by TopoLVM because the data could be lost when the migration is failed.**
+
+**Please test the migration procedure before you execute the migration. Also, since the migration procedure is complicated, please consider using TopoLVM as topolvm.cybozu.com without migration.**
+
+1. Deploy the `topolvm.io` version of TopoLVM to a different namespace from the `topolvm.cybozu.com` version of TopoLVM (when used with the `topolvm.cybozu.com` version of TopoLVM,  names of cluster-wide resources such as StorageClass names need to be changed).
+1. Create a new PV(C) using the topolvm.io version of TopoLVM.
+1. Copy the LVM volume data created by the `topolvm.cybozu.com` version of TopoLVM to new LVM volumes created by the `topolvm.io` version of TopoLVM by some ways such as `rsync` command (You can know the targets LV Name of LVM from the values of PV's  `.spec.csi.volumeHandle`).
+1. Start pods using volumes created by the `topolvm.io` version of TopoLVM and confirm the data has been migrated without problems.
+1. Remove PVCs and PVs that use the `topolvm.cybozu.com` version of TopoLVM.
+1. Remove old (using `topolvm.cybozu.com`) TopoLVM.
+
+## NOTE
+
+### Alternative consideration
+
+#### Automatic migration of group names
+
+We considered a method to automatically perform the migration as described in `Design details` using script or Kubernetes Controller, but resources such as PersistentVolume and LogicalVolume need to be recreated.
+There is a risk that the real LVM volume will be deleted due to the re-creation, and it was judged that the risk is high for automatic migration.
diff --git a/docs/topolvm-controller.md b/docs/topolvm-controller.md
index 16588d81..b6b6de76 100644
--- a/docs/topolvm-controller.md
+++ b/docs/topolvm-controller.md
@@ -23,8 +23,8 @@ Webhooks
 
 ### `/pod/mutate`
 
-Mutate new Pods to add `capacity.topolvm.cybozu.com/<device-class>` annotations to the pod
-and `topolvm.cybozu.com/capacity` resource request to its first container.
+Mutate new Pods to add `capacity.topolvm.io/<device-class>` annotations to the pod
+and `topolvm.io/capacity` resource request to its first container.
 These annotations and the resource request will be used by
 [`topolvm-scheduler`](./topolvm-scheduler.md) to filter and score Nodes.
 
@@ -47,10 +47,10 @@ kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm
-provisioner: topolvm.cybozu.com            # topolvm-scheduler works only for StorageClass with this provisioner.
+provisioner: topolvm.io            # topolvm-scheduler works only for StorageClass with this provisioner.
 parameters:
   "csi.storage.k8s.io/fstype": "xfs"
-  "topolvm.cybozu.com/device-class": "ssd"
+  "topolvm.io/device-class": "ssd"
 volumeBindingMode: WaitForFirstConsumer
 ---
 kind: PersistentVolumeClaim
@@ -86,25 +86,25 @@ spec:
       claimName: local-pvc1                # have the above PVC
 ```
 
-The hook inserts `capacity.topolvm.cybozu.com/<device-class>` to the annotations
-and `topolvm.cybozu.com/capacity` to the first container as follows:
+The hook inserts `capacity.topolvm.io/<device-class>` to the annotations
+and `topolvm.io/capacity` to the first container as follows:
 
 ```yaml
 metadata:
   annotations:
-    capacity.topolvm.cybozu.com/ssd: "1073741824"
+    capacity.topolvm.io/ssd: "1073741824"
 spec:
   containers:
   - name: testhttpd
     resources:
       limits:
-        topolvm.cybozu.com/capacity: "1"
+        topolvm.io/capacity: "1"
       requests:
-        topolvm.cybozu.com/capacity: "1"
+        topolvm.io/capacity: "1"
 ```
 
-If the specified StorageClass does not have `topolvm.cybozu.com/device-class` parameter,
-it will be annotated with `capacity.topolvm.cybozu.com/00default`.
+If the specified StorageClass does not have `topolvm.io/device-class` parameter,
+it will be annotated with `capacity.topolvm.io/00default`.
 
 Below is an example for TopoLVM generic ephemeral volumes:
 
@@ -136,21 +136,21 @@ spec:
             storageClassName: topolvm # reference the above StorageClass
 ```
 
-The hook inserts `capacity.topolvm.cybozu.com/<device-class>` to the annotations
-and `topolvm.cybozu.com/capacity` to the first container as follows:
+The hook inserts `capacity.topolvm.io/<device-class>` to the annotations
+and `topolvm.io/capacity` to the first container as follows:
 
 ```yaml
 metadata:
   annotations:
-    capacity.topolvm.cybozu.com/ssd: "1073741824"
+    capacity.topolvm.io/ssd: "1073741824"
 spec:
   containers:
   - name: ubuntu
     resources:
       limits:
-        topolvm.cybozu.com/capacity: "1"
+        topolvm.io/capacity: "1"
       requests:
-        topolvm.cybozu.com/capacity: "1"
+        topolvm.io/capacity: "1"
 ```
 
 Controllers
@@ -158,7 +158,7 @@ Controllers
 
 ### Node finalizer
 
-`topolvm-metrics` adds `topolvm.cybozu.com/node` finalizer.
+`topolvm-metrics` adds `topolvm.io/node` finalizer.
 
 When a Node is being deleted, the controller deletes all PVCs and LogicalVolumes for TopoLVM
 on the deleting node. 
diff --git a/docs/topolvm-node.md b/docs/topolvm-node.md
index 59afec6d..99a6684b 100644
--- a/docs/topolvm-node.md
+++ b/docs/topolvm-node.md
@@ -95,12 +95,12 @@ free space in the LVM volume group in bytes.
 Node resource
 -------------
 
-`topolvm-node` adds `capacity.topolvm.cybozu.com/<device-class>` annotations
-for each device-class and `capacity.topolvm.cybozu.com/00default` annotation 
+`topolvm-node` adds `capacity.topolvm.io/<device-class>` annotations
+for each device-class and `capacity.topolvm.io/00default` annotation 
 for the default device-class to the corresponding `Node` resource of the running node.
 The value is the free storage capacity reported by `lvmd` in bytes.
 
-It also adds `topolvm.cybozu.com/node` finalizer to the `Node`.
+It also adds `topolvm.io/node` finalizer to the `Node`.
 The finalizer will be processed by [`topolvm-controller`](./topolvm-controller.md)
 to clean up PVCs and associated Pods bound to the node.
 
diff --git a/docs/topolvm-scheduler.md b/docs/topolvm-scheduler.md
index 4f9f6d90..be44f697 100644
--- a/docs/topolvm-scheduler.md
+++ b/docs/topolvm-scheduler.md
@@ -19,7 +19,7 @@ Scheduler policy
         "prioritizeVerb": "prioritize",
         "managedResources":
         [{
-          "name": "topolvm.cybozu.com/capacity",
+          "name": "topolvm.io/capacity",
           "ignoredByScheduler": true
         }],
         "nodeCacheCapable": false
@@ -27,7 +27,7 @@ Scheduler policy
 }
 ```
 
-As shown, only pods that request `topolvm.cybozu.com/capacity` resource are
+As shown, only pods that request `topolvm.io/capacity` resource are
 managed by `topolvm-scheduler`.
 
 Verbs
@@ -42,7 +42,7 @@ The extender provides two verbs:
 
 This verb filters out nodes whose volume groups have not enough free space.
 
-Volume group capacity is identified from the value of `capacity.topolvm.cybozu.com/<device-class>`
+Volume group capacity is identified from the value of `capacity.topolvm.io/<device-class>`
 annotation.
 
 ### `prioritize`
diff --git a/docs/user-manual.md b/docs/user-manual.md
index c186b00f..63528223 100644
--- a/docs/user-manual.md
+++ b/docs/user-manual.md
@@ -24,20 +24,20 @@ kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 parameters:
   "csi.storage.k8s.io/fstype": "xfs"
-  "topolvm.cybozu.com/device-class": "ssd"
+  "topolvm.io/device-class": "ssd"
 volumeBindingMode: WaitForFirstConsumer
 allowVolumeExpansion: true
 ```
 
-`provisioner` must be `topolvm.cybozu.com`.
+`provisioner` must be `topolvm.io`.
 
 `parameters` are optional.
 To specify a filesystem type, give `csi.storage.k8s.io/fstype` parameter.
-To specify a device-class name to be used, give `topolvm.cybozu.com/device-class` parameter. 
-If no `topolvm.cybozu.com/device-class` is specified, the default device-class is used.
+To specify a device-class name to be used, give `topolvm.io/device-class` parameter. 
+If no `topolvm.io/device-class` is specified, the default device-class is used.
 
 Supported filesystems are: `ext4` and `xfs`.
 
diff --git a/driver/controller.go b/driver/controller.go
index 45dc82e0..8e200e7d 100644
--- a/driver/controller.go
+++ b/driver/controller.go
@@ -34,7 +34,7 @@ type controllerService struct {
 func (s controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
 	capabilities := req.GetVolumeCapabilities()
 	source := req.GetVolumeContentSource()
-	deviceClass := req.GetParameters()[topolvm.DeviceClassKey]
+	deviceClass := req.GetParameters()[topolvm.GetDeviceClassKey()]
 
 	ctrlLogger.Info("CreateVolume called",
 		"name", req.GetName(),
@@ -126,7 +126,7 @@ func (s controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolu
 		} else {
 			sourceNode := sourceVol.Spec.NodeName
 			for _, topo := range requirements.Preferred {
-				if v, ok := topo.GetSegments()[topolvm.TopologyNodeKey]; ok {
+				if v, ok := topo.GetSegments()[topolvm.GetTopologyNodeKey()]; ok {
 					if v == sourceNode {
 						node = v
 						break
@@ -135,7 +135,7 @@ func (s controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolu
 			}
 			if node == "" {
 				for _, topo := range requirements.Requisite {
-					if v, ok := topo.GetSegments()[topolvm.TopologyNodeKey]; ok {
+					if v, ok := topo.GetSegments()[topolvm.GetTopologyNodeKey()]; ok {
 						if v == sourceNode {
 							node = v
 							break
@@ -168,21 +168,21 @@ func (s controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolu
 			node = nodeName
 		} else {
 			for _, topo := range requirements.Preferred {
-				if v, ok := topo.GetSegments()[topolvm.TopologyNodeKey]; ok {
+				if v, ok := topo.GetSegments()[topolvm.GetTopologyNodeKey()]; ok {
 					node = v
 					break
 				}
 			}
 			if node == "" {
 				for _, topo := range requirements.Requisite {
-					if v, ok := topo.GetSegments()[topolvm.TopologyNodeKey]; ok {
+					if v, ok := topo.GetSegments()[topolvm.GetTopologyNodeKey()]; ok {
 						node = v
 						break
 					}
 				}
 			}
 			if node == "" {
-				return nil, status.Errorf(codes.InvalidArgument, "cannot find key '%s' in accessibility_requirements", topolvm.TopologyNodeKey)
+				return nil, status.Errorf(codes.InvalidArgument, "cannot find key '%s' in accessibility_requirements", topolvm.GetTopologyNodeKey())
 			}
 		}
 	}
@@ -210,7 +210,7 @@ func (s controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolu
 			ContentSource: source,
 			AccessibleTopology: []*csi.Topology{
 				{
-					Segments: map[string]string{topolvm.TopologyNodeKey: node},
+					Segments: map[string]string{topolvm.GetTopologyNodeKey(): node},
 				},
 			},
 		},
@@ -420,7 +420,7 @@ func (s controllerService) GetCapacity(ctx context.Context, req *csi.GetCapacity
 		ctrlLogger.Info("capability argument is not nil, but TopoLVM ignores it")
 	}
 
-	deviceClass := req.GetParameters()[topolvm.DeviceClassKey]
+	deviceClass := req.GetParameters()[topolvm.GetDeviceClassKey()]
 
 	var capacity int64
 	switch topology {
@@ -431,9 +431,9 @@ func (s controllerService) GetCapacity(ctx context.Context, req *csi.GetCapacity
 			return nil, status.Error(codes.Internal, err.Error())
 		}
 	default:
-		v, ok := topology.Segments[topolvm.TopologyNodeKey]
+		v, ok := topology.Segments[topolvm.GetTopologyNodeKey()]
 		if !ok {
-			err := fmt.Errorf("%s is not found in req.AccessibleTopology", topolvm.TopologyNodeKey)
+			err := fmt.Errorf("%s is not found in req.AccessibleTopology", topolvm.GetTopologyNodeKey())
 			ctrlLogger.Error(err, "target node key is not found")
 			return &csi.GetCapacityResponse{AvailableCapacity: 0}, nil
 		}
diff --git a/driver/identity.go b/driver/identity.go
index d3372919..28060a6d 100644
--- a/driver/identity.go
+++ b/driver/identity.go
@@ -32,7 +32,7 @@ type identityService struct {
 func (s identityService) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
 	idLogger.Info("GetPluginInfo", "req", req.String())
 	return &csi.GetPluginInfoResponse{
-		Name:          topolvm.PluginName,
+		Name:          topolvm.GetPluginName(),
 		VendorVersion: topolvm.Version,
 	}, nil
 }
diff --git a/driver/k8s/logicalvolume_service.go b/driver/k8s/logicalvolume_service.go
index 546d11bc..dcaf1511 100644
--- a/driver/k8s/logicalvolume_service.go
+++ b/driver/k8s/logicalvolume_service.go
@@ -8,7 +8,9 @@ import (
 	"time"
 
 	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
 	topolvmv1 "github.com/topolvm/topolvm/api/v1"
+	clientwrapper "github.com/topolvm/topolvm/client"
 	"github.com/topolvm/topolvm/getter"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
@@ -29,7 +31,7 @@ type LogicalVolumeService struct {
 		client.Writer
 		client.StatusClient
 	}
-	getter       *getter.RetryMissingGetter
+	getter       getter.Interface
 	volumeGetter *volumeGetter
 	mu           sync.Mutex
 }
@@ -42,6 +44,37 @@ var (
 	logger = ctrl.Log.WithName("LogicalVolume")
 )
 
+type retryMissingGetter struct {
+	cacheReader client.Reader
+	apiReader   client.Reader
+	getter      getter.Interface
+}
+
+func newRetryMissingGetter(cacheReader client.Reader, apiReader client.Reader) *retryMissingGetter {
+	return &retryMissingGetter{
+		cacheReader: cacheReader,
+		apiReader:   apiReader,
+		getter:      getter.NewRetryMissingGetter(cacheReader, apiReader),
+	}
+}
+
+func (r *retryMissingGetter) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
+	var lv *topolvmv1.LogicalVolume
+	var ok bool
+	if lv, ok = obj.(*topolvmv1.LogicalVolume); !ok {
+		return r.getter.Get(ctx, key, obj)
+	}
+
+	err := r.cacheReader.Get(ctx, key, lv)
+	if err == nil {
+		return nil
+	}
+	if !apierrors.IsNotFound(err) {
+		return err
+	}
+	return r.apiReader.Get(ctx, key, lv)
+}
+
 // This type is a safe guard to prohibit calling List from LogicalVolumeService directly.
 type volumeGetter struct {
 	cacheReader client.Reader
@@ -86,24 +119,34 @@ func (v *volumeGetter) Get(ctx context.Context, volumeID string) (*topolvmv1.Log
 	return foundLv, nil
 }
 
-//+kubebuilder:rbac:groups=topolvm.cybozu.com,resources=logicalvolumes,verbs=get;list;watch;create;delete
+//+kubebuilder:rbac:groups=topolvm.io,resources=logicalvolumes,verbs=get;list;watch;create;delete
 //+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch
 
 // NewLogicalVolumeService returns LogicalVolumeService.
 func NewLogicalVolumeService(mgr manager.Manager) (*LogicalVolumeService, error) {
 	ctx := context.Background()
-	err := mgr.GetFieldIndexer().IndexField(ctx, &topolvmv1.LogicalVolume{}, indexFieldVolumeID,
-		func(o client.Object) []string {
+	if topolvm.UseLegacy() {
+		err := mgr.GetFieldIndexer().IndexField(ctx, &topolvmlegacyv1.LogicalVolume{}, indexFieldVolumeID, func(o client.Object) []string {
+			return []string{o.(*topolvmlegacyv1.LogicalVolume).Status.VolumeID}
+		})
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		err := mgr.GetFieldIndexer().IndexField(ctx, &topolvmv1.LogicalVolume{}, indexFieldVolumeID, func(o client.Object) []string {
 			return []string{o.(*topolvmv1.LogicalVolume).Status.VolumeID}
 		})
-	if err != nil {
-		return nil, err
+		if err != nil {
+			return nil, err
+		}
 	}
 
+	client := clientwrapper.NewWrappedClient(mgr.GetClient())
+	apiReader := clientwrapper.NewWrappedReader(mgr.GetAPIReader(), mgr.GetClient().Scheme())
 	return &LogicalVolumeService{
-		writer:       mgr.GetClient(),
-		getter:       getter.NewRetryMissingGetter(mgr.GetClient(), mgr.GetAPIReader()),
-		volumeGetter: &volumeGetter{cacheReader: mgr.GetClient(), apiReader: mgr.GetAPIReader()},
+		writer:       client,
+		getter:       newRetryMissingGetter(client, apiReader),
+		volumeGetter: &volumeGetter{cacheReader: client, apiReader: apiReader},
 	}, nil
 }
 
@@ -116,10 +159,6 @@ func (s *LogicalVolumeService) CreateVolume(ctx context.Context, node, dc, name,
 	// if the create volume request has no source, proceed with regular lv creation.
 	if sourceName == "" {
 		lv = &topolvmv1.LogicalVolume{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "LogicalVolume",
-				APIVersion: "topolvm.cybozu.com/v1",
-			},
 			ObjectMeta: metav1.ObjectMeta{
 				Name: name,
 			},
@@ -134,10 +173,6 @@ func (s *LogicalVolumeService) CreateVolume(ctx context.Context, node, dc, name,
 	} else {
 		// On the other hand, if a volume has a datasource, create a thin snapshot of the source volume with READ-WRITE access.
 		lv = &topolvmv1.LogicalVolume{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "LogicalVolume",
-				APIVersion: "topolvm.cybozu.com/v1",
-			},
 			ObjectMeta: metav1.ObjectMeta{
 				Name: name,
 			},
@@ -226,10 +261,6 @@ func (s *LogicalVolumeService) DeleteVolume(ctx context.Context, volumeID string
 func (s *LogicalVolumeService) CreateSnapshot(ctx context.Context, node, dc, sourceVol, sname, accessType string, snapSize resource.Quantity) (string, error) {
 	logger.Info("CreateSnapshot called", "name", sname)
 	snapshotLV := &topolvmv1.LogicalVolume{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "LogicalVolume",
-			APIVersion: "topolvm.cybozu.com/v1",
-		},
 		ObjectMeta: metav1.ObjectMeta{
 			Name: sname,
 		},
@@ -338,7 +369,7 @@ func (s *LogicalVolumeService) UpdateSpecSize(ctx context.Context, volumeID stri
 		if lv.Annotations == nil {
 			lv.Annotations = make(map[string]string)
 		}
-		lv.Annotations[topolvm.ResizeRequestedAtKey] = time.Now().UTC().String()
+		lv.Annotations[topolvm.GetResizeRequestedAtKey()] = time.Now().UTC().String()
 
 		if err := s.writer.Update(ctx, lv); err != nil {
 			if apierrors.IsConflict(err) {
diff --git a/driver/k8s/node_service.go b/driver/k8s/node_service.go
index 289ad0ce..d4046a55 100644
--- a/driver/k8s/node_service.go
+++ b/driver/k8s/node_service.go
@@ -8,7 +8,6 @@ import (
 	"github.com/topolvm/topolvm"
 	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
 )
 
 // ErrNodeNotFound represents the error that node is not found.
@@ -22,8 +21,8 @@ type NodeService struct {
 }
 
 // NewNodeService returns NodeService.
-func NewNodeService(mgr manager.Manager) *NodeService {
-	return &NodeService{reader: mgr.GetClient()}
+func NewNodeService(r client.Reader) *NodeService {
+	return &NodeService{reader: r}
 }
 
 func (s NodeService) getNodes(ctx context.Context) (*corev1.NodeList, error) {
@@ -39,7 +38,7 @@ func (s NodeService) extractCapacityFromAnnotation(node *corev1.Node, deviceClas
 	if deviceClass == topolvm.DefaultDeviceClassName {
 		deviceClass = topolvm.DefaultDeviceClassAnnotationName
 	}
-	c, ok := node.Annotations[topolvm.CapacityKeyPrefix+deviceClass]
+	c, ok := node.Annotations[topolvm.GetCapacityKeyPrefix()+deviceClass]
 	if !ok {
 		return 0, ErrDeviceClassNotFound
 	}
@@ -65,7 +64,7 @@ func (s NodeService) GetCapacityByTopologyLabel(ctx context.Context, topology, d
 	}
 
 	for _, node := range nl.Items {
-		if v, ok := node.Labels[topolvm.TopologyNodeKey]; ok {
+		if v, ok := node.Labels[topolvm.GetTopologyNodeKey()]; ok {
 			if v != topology {
 				continue
 			}
diff --git a/driver/node.go b/driver/node.go
index 80e561a5..78bf3d7d 100644
--- a/driver/node.go
+++ b/driver/node.go
@@ -534,7 +534,7 @@ func (s *nodeService) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoReque
 		NodeId: s.nodeName,
 		AccessibleTopology: &csi.Topology{
 			Segments: map[string]string{
-				topolvm.TopologyNodeKey: s.nodeName,
+				topolvm.GetTopologyNodeKey(): s.nodeName,
 			},
 		},
 	}, nil
diff --git a/example/Makefile b/example/Makefile
index 0bd21fa5..68176337 100644
--- a/example/Makefile
+++ b/example/Makefile
@@ -1,3 +1,5 @@
+USE_LEGACY ?= ""
+
 KUBERNETES_VERSION := 1.24.2
 HELM_VERSION := 3.9.0
 TOPOLVM_VERSION := 0.15.3
@@ -8,7 +10,7 @@ KIND_CLUSTER_NAME := topolvm-example
 SUDO := sudo
 CURL := curl -sSLf
 BINDIR := $(shell pwd)/bin
-TMPDIR := /tmp/topolvm
+TMPDIR := /tmp/topolvm-example
 HELM := $(BINDIR)/helm
 KUBECTL := $(BINDIR)/kubectl
 KIND := ../bin/kind
@@ -18,6 +20,17 @@ BACKING_STORE := ./build
 
 BUILD_IMAGE := false
 
+HELM_VALUES_FILE := values.yaml
+SCHEDULER_CONFIG := scheduler-config.yaml
+LOGICALVOLUME_GK_NAME := logicalvolume.topolvm.io
+DOMAIN_NAME := topolvm.io
+ifneq ($(USE_LEGACY),"")
+HELM_VALUES_FILE := values_legacy.yaml
+SCHEDULER_CONFIG := scheduler-config-legacy.yaml
+LOGICALVOLUME_GK_NAME := logicalvolume.topolvm.cybozu.com
+DOMAIN_NAME := topolvm.cybozu.com
+endif
+
 $(KUBECTL):
 	mkdir -p $(BINDIR)
 	$(CURL) https://storage.googleapis.com/kubernetes-release/release/v$(KUBERNETES_VERSION)/bin/linux/amd64/kubectl -o $(KUBECTL)
@@ -28,7 +41,9 @@ build/topolvm.img: $(GO_FILES)
 	$(MAKE) -C .. tag IMAGE_PREFIX=ghcr.io/topolvm/ IMAGE_TAG=$(TOPOLVM_VERSION)
 	docker save -o $@ ghcr.io/topolvm/topolvm-with-sidecar:$(TOPOLVM_VERSION)
 
+.PHONY: run
 run:
+	rm -f $(TMPDIR)/scheduler/scheduler-config.yaml
 	$(MAKE) start-lvmd
 	$(MAKE) launch-kind
 	if [ "$(BUILD_IMAGE)" = "true" ]; then \
@@ -37,17 +52,20 @@ run:
 	fi
 	$(KUBECTL) apply -f https://github.com/cert-manager/cert-manager/releases/download/$(CERT_MANAGER_VERSION)/cert-manager.crds.yaml
 	$(KUBECTL) create namespace topolvm-system
-	$(KUBECTL) label namespace topolvm-system topolvm.cybozu.com/webhook=ignore
-	$(KUBECTL) label namespace kube-system topolvm.cybozu.com/webhook=ignore
+	$(KUBECTL) label namespace topolvm-system $(DOMAIN_NAME)/webhook=ignore
+	$(KUBECTL) label namespace kube-system $(DOMAIN_NAME)/webhook=ignore
 	$(HELM) repo add jetstack https://charts.jetstack.io
 	$(HELM) repo update
 	$(HELM) dependency build ../charts/topolvm/
-	$(HELM) install --namespace=topolvm-system topolvm ../charts/topolvm/ -f ./values.yaml
+	$(HELM) install --namespace=topolvm-system topolvm ../charts/topolvm/ -f $(HELM_VALUES_FILE)
 	$(KUBECTL) wait --for=condition=available --timeout=120s -n topolvm-system deployments/topolvm-controller
+	$(KUBECTL) wait --for=condition=ready --timeout=120s -n topolvm-system -l="app.kubernetes.io/component=controller,app.kubernetes.io/name=topolvm" pod
 	$(KUBECTL) wait --for=condition=ready --timeout=120s -n topolvm-system certificate/topolvm-mutatingwebhook
 	timeout 120 sh -c "until $(KUBECTL) apply -f podpvc.yaml; do sleep 10; done"
-	$(KUBECTL) wait --for=condition=ready --timeout=60s -n default pod -l app=example
+	$(KUBECTL) wait --for=condition=ready --timeout=120s -n default pod -l app=example
+	[ $$($(KUBECTL) get --no-headers=true $(LOGICALVOLUME_GK_NAME) | wc -l) -ne 0 ]
 
+.PHONY: setup
 setup: $(KUBECTL)
 	$(SUDO) apt-get update
 	$(SUDO) apt-get install -y lvm2 xfsprogs thin-provisioning-tools
@@ -57,10 +75,11 @@ setup: $(KUBECTL)
 	$(CURL) https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz \
 	  | tar xvz -C $(BINDIR) --strip-components 1 linux-amd64/helm
 
+.PHONY: stop-lvmd
 clean: stop-lvmd
-	rm -rf bin/ build/
+	rm -rf bin/ build/ $(TMPDIR)
 
-$(TMPDIR)/scheduler/scheduler-config.yaml: ../deploy/scheduler-config/scheduler-config-v1beta2.yaml
+$(TMPDIR)/scheduler/scheduler-config.yaml: ../deploy/scheduler-config/$(SCHEDULER_CONFIG)
 	mkdir -p $(TMPDIR)/scheduler
 	cp $< $@
 
@@ -68,12 +87,14 @@ $(TMPDIR)/lvmd/lvmd.yaml: ../deploy/lvmd-config/lvmd.yaml
 	mkdir -p $(TMPDIR)/lvmd
 	sed -e 's=/run/topolvm/lvmd.sock=$(TMPDIR)/lvmd/lvmd.sock=' $< > $@
 
-launch-kind: $(TMPDIR)/scheduler/scheduler-config.yaml kind/topolvm-cluster.yaml
+.PHONY: launch-kind
+launch-kind: $(TMPDIR)/scheduler/scheduler-config.yaml
 	$(MAKE) shutdown-kind
 	$(SUDO) rm -rf $(TMPDIR)/controller $(TMPDIR)/worker*
 	sed 's/@KUBERNETES_VERSION@/$(KUBERNETES_VERSION)/; s=@TMPDIR@=$(TMPDIR)='  kind/topolvm-cluster.yaml > $(TMPDIR)/topolvm-cluster.yaml
 	$(KIND) create cluster --name=$(KIND_CLUSTER_NAME) --config $(TMPDIR)/topolvm-cluster.yaml --image kindest/node:v$(KUBERNETES_VERSION)
 
+.PHONY: shutdown-kind
 shutdown-kind:
 	$(KIND) delete cluster --name=$(KIND_CLUSTER_NAME) || true
 	for d in $$($(SUDO) find $(TMPDIR) -type d); do \
@@ -82,6 +103,7 @@ shutdown-kind:
 		fi; \
 	done
 
+.PHONY: start-lvmd
 start-lvmd: $(TMPDIR)/lvmd/lvmd.yaml
 	go build -o build/lvmd ../pkg/lvmd
 	if [ -f $(BACKING_STORE)/backing_store ]; then $(MAKE) stop-lvmd; fi; \
@@ -90,9 +112,10 @@ start-lvmd: $(TMPDIR)/lvmd/lvmd.yaml
 	truncate --size=20G $(BACKING_STORE)/backing_store; \
 	$(SUDO) losetup -f $(BACKING_STORE)/backing_store; \
 	$(SUDO) vgcreate -f -y myvg1 $$($(SUDO) losetup -j $(BACKING_STORE)/backing_store | cut -d: -f1); \
-	$(SUDO) lvcreate -T myvg1/thinpool -L 2G; \
+	$(SUDO) lvcreate -T myvg1/thinpool -L 12G; \
 	$(SUDO) systemd-run --unit=lvmd.service $(shell pwd)/build/lvmd --config=$(TMPDIR)/lvmd/lvmd.yaml; \
 
+.PHONY: stop-lvmd
 stop-lvmd:
 	$(MAKE) shutdown-kind
 	if systemctl is-active -q lvmd.service; then $(SUDO) systemctl stop lvmd.service; fi; \
@@ -102,5 +125,3 @@ stop-lvmd:
 		$(SUDO) losetup -d $$($(SUDO) losetup -j $(BACKING_STORE)/backing_store | cut -d: -f1); \
 		rm -f $(BACKING_STORE)/backing_store; \
 	fi
-
-.PHONY: launch-kind shutdown-kind start-lvmd stop-lvmd run setup clean
diff --git a/example/values.yaml b/example/values.yaml
index 1113e18b..27536833 100644
--- a/example/values.yaml
+++ b/example/values.yaml
@@ -24,4 +24,4 @@ storageClasses:
       volumeBindingMode: WaitForFirstConsumer
       allowVolumeExpansion: true
       additionalParameters:
-        "topolvm.cybozu.com/device-class": "ssd-thin"
+        "topolvm.io/device-class": "ssd-thin"
diff --git a/example/values_legacy.yaml b/example/values_legacy.yaml
new file mode 100644
index 00000000..54edf84c
--- /dev/null
+++ b/example/values_legacy.yaml
@@ -0,0 +1,29 @@
+controller:
+  replicaCount: 1
+
+lvmd:
+  managed: false
+
+podSecurityPolicy:
+  create: false
+
+cert-manager:
+  enabled: true
+
+storageClasses:
+  - name: topolvm-provisioner
+    storageClass:
+      fsType: xfs
+      isDefaultClass: false
+      volumeBindingMode: WaitForFirstConsumer
+      allowVolumeExpansion: true
+  - name: topolvm-provisioner-thin
+    storageClass:
+      fsType: xfs
+      isDefaultClass: false
+      volumeBindingMode: WaitForFirstConsumer
+      allowVolumeExpansion: true
+      additionalParameters:
+        "topolvm.cybozu.com/device-class": "ssd-thin"
+
+useLegacy: true
diff --git a/getter/interface.go b/getter/interface.go
new file mode 100644
index 00000000..91213309
--- /dev/null
+++ b/getter/interface.go
@@ -0,0 +1,11 @@
+package getter
+
+import (
+	"context"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type Interface interface {
+	Get(ctx context.Context, key client.ObjectKey, obj client.Object) error
+}
diff --git a/hook/mutate_pod.go b/hook/mutate_pod.go
index cb07df98..267d3c68 100644
--- a/hook/mutate_pod.go
+++ b/hook/mutate_pod.go
@@ -21,7 +21,7 @@ import (
 
 var pmLogger = ctrl.Log.WithName("pod-mutator")
 
-//+kubebuilder:webhook:failurePolicy=fail,matchPolicy=equivalent,groups=core,resources=pods,verbs=create,versions=v1,name=pod-hook.topolvm.cybozu.com,path=/pod/mutate,mutating=true,sideEffects=none,admissionReviewVersions={v1,v1beta1}
+//+kubebuilder:webhook:failurePolicy=fail,matchPolicy=equivalent,groups=core,resources=pods,verbs=create,versions=v1,name=pod-hook.topolvm.io,path=/pod/mutate,mutating=true,sideEffects=none,admissionReviewVersions={v1,v1beta1}
 //+kubebuilder:rbac:groups=core,resources=persistentvolumeclaims,verbs=get;list;watch
 //+kubebuilder:rbac:groups=storage.k8s.io,resources=storageclasses,verbs=get;list;watch
 
@@ -79,17 +79,17 @@ func (m *podMutator) Handle(ctx context.Context, req admission.Request) admissio
 	if ctnr.Resources.Requests == nil {
 		ctnr.Resources.Requests = corev1.ResourceList{}
 	}
-	ctnr.Resources.Requests[topolvm.CapacityResource] = *quantity
+	ctnr.Resources.Requests[topolvm.GetCapacityResource()] = *quantity
 	if ctnr.Resources.Limits == nil {
 		ctnr.Resources.Limits = corev1.ResourceList{}
 	}
-	ctnr.Resources.Limits[topolvm.CapacityResource] = *quantity
+	ctnr.Resources.Limits[topolvm.GetCapacityResource()] = *quantity
 
 	if pod.Annotations == nil {
 		pod.Annotations = make(map[string]string)
 	}
 	for dc, capacity := range capacities {
-		pod.Annotations[topolvm.CapacityKeyPrefix+dc] = strconv.FormatInt(capacity, 10)
+		pod.Annotations[topolvm.GetCapacityKeyPrefix()+dc] = strconv.FormatInt(capacity, 10)
 	}
 
 	marshaledPod, err := json.Marshal(pod)
@@ -119,7 +119,7 @@ func (t *targetSC) Get(ctx context.Context, name string) (*storagev1.StorageClas
 		}
 		return nil, err
 	}
-	if sc.Provisioner != topolvm.PluginName {
+	if sc.Provisioner != topolvm.GetPluginName() {
 		t.cache[name] = nil
 		return nil, nil
 	}
@@ -214,7 +214,7 @@ func (m *podMutator) pvcCapacity(
 			requested = ((req.Value()-1)>>30 + 1) << 30
 		}
 	}
-	dc, ok := sc.Parameters[topolvm.DeviceClassKey]
+	dc, ok := sc.Parameters[topolvm.GetDeviceClassKey()]
 	if !ok {
 		dc = topolvm.DefaultDeviceClassAnnotationName
 	}
@@ -248,7 +248,7 @@ func (m *podMutator) ephemeralCapacity(
 			requested = ((req.Value()-1)>>30 + 1) << 30
 		}
 	}
-	dc, ok := sc.Parameters[topolvm.DeviceClassKey]
+	dc, ok := sc.Parameters[topolvm.GetDeviceClassKey()]
 	if !ok {
 		dc = topolvm.DefaultDeviceClassAnnotationName
 	}
diff --git a/lvmd/healthservice.go b/lvmd/healthservice.go
deleted file mode 100644
index a57b8e80..00000000
--- a/lvmd/healthservice.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package lvmd
-
-import (
-	"context"
-
-	"google.golang.org/grpc/health/grpc_health_v1"
-)
-
-func NewHealthService() grpc_health_v1.HealthServer {
-	return &healthService{}
-}
-
-type healthService struct {
-	grpc_health_v1.UnimplementedHealthServer
-}
-
-func (*healthService) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
-	return &grpc_health_v1.HealthCheckResponse{Status: grpc_health_v1.HealthCheckResponse_SERVING}, nil
-}
diff --git a/pkg/lvmd/cmd/config.go b/pkg/lvmd/cmd/config.go
deleted file mode 100644
index 1a85ea23..00000000
--- a/pkg/lvmd/cmd/config.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package cmd
-
-import (
-	"os"
-
-	"github.com/cybozu-go/log"
-	"github.com/topolvm/topolvm"
-	"github.com/topolvm/topolvm/lvmd"
-	"sigs.k8s.io/yaml"
-)
-
-// Config represents configuration parameters for lvmd
-type Config struct {
-	// SocketName is Unix domain socket name
-	SocketName string `json:"socket-name"`
-	// DeviceClasses is
-	DeviceClasses []*lvmd.DeviceClass `json:"device-classes"`
-}
-
-var config = &Config{
-	SocketName: topolvm.DefaultLVMdSocket,
-}
-
-func loadConfFile(cfgFilePath string) error {
-	b, err := os.ReadFile(cfgFilePath)
-	if err != nil {
-		return err
-	}
-	err = yaml.Unmarshal(b, config)
-	if err != nil {
-		return err
-	}
-	log.Info("configuration file loaded: ", map[string]interface{}{
-		"device_classes": config.DeviceClasses,
-		"socket_name":    config.SocketName,
-		"file_name":      cfgFilePath,
-	})
-	return nil
-}
diff --git a/pkg/lvmd/cmd/health.go b/pkg/lvmd/cmd/health.go
deleted file mode 100644
index 8dd433ed..00000000
--- a/pkg/lvmd/cmd/health.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package cmd
-
-import (
-	"context"
-	"fmt"
-	"net"
-
-	"github.com/spf13/cobra"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/health/grpc_health_v1"
-)
-
-var healthCmd = &cobra.Command{
-	Use:   "health",
-	Short: "Health check for lvmd server",
-	RunE: func(cmd *cobra.Command, args []string) error {
-		cmd.SilenceUsage = true
-		return healthSubMain(config)
-	},
-}
-
-func healthSubMain(config *Config) error {
-	err := loadConfFile(cfgFilePath)
-	if err != nil {
-		return err
-	}
-	dialer := &net.Dialer{}
-	dialFunc := func(ctx context.Context, a string) (net.Conn, error) {
-		return dialer.DialContext(ctx, "unix", a)
-	}
-	conn, err := grpc.Dial(config.SocketName, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialFunc))
-	if err != nil {
-		return err
-	}
-	defer conn.Close()
-	client := grpc_health_v1.NewHealthClient(conn)
-
-	ctx := context.Background()
-	res, err := client.Check(ctx, &grpc_health_v1.HealthCheckRequest{})
-	if err != nil {
-		return err
-	}
-	if status := res.GetStatus(); status != grpc_health_v1.HealthCheckResponse_SERVING {
-		return fmt.Errorf("lvmd does not working: %s", status.String())
-	}
-	return nil
-}
-
-func init() {
-	rootCmd.AddCommand(healthCmd)
-}
diff --git a/pkg/lvmd/cmd/root.go b/pkg/lvmd/cmd/root.go
index c193a8c6..525bd1d7 100644
--- a/pkg/lvmd/cmd/root.go
+++ b/pkg/lvmd/cmd/root.go
@@ -16,11 +16,23 @@ import (
 	"github.com/topolvm/topolvm/lvmd/command"
 	"github.com/topolvm/topolvm/lvmd/proto"
 	"google.golang.org/grpc"
-	"google.golang.org/grpc/health/grpc_health_v1"
+	"sigs.k8s.io/yaml"
 )
 
 var cfgFilePath string
 
+// Config represents configuration parameters for lvmd
+type Config struct {
+	// SocketName is Unix domain socket name
+	SocketName string `json:"socket-name"`
+	// DeviceClasses is
+	DeviceClasses []*lvmd.DeviceClass `json:"device-classes"`
+}
+
+var config = &Config{
+	SocketName: topolvm.DefaultLVMdSocket,
+}
+
 // rootCmd represents the base command when called without any subcommands
 var rootCmd = &cobra.Command{
 	Use:     "lvmd",
@@ -47,10 +59,19 @@ func subMain() error {
 		return err
 	}
 
-	err = loadConfFile(cfgFilePath)
+	b, err := os.ReadFile(cfgFilePath)
 	if err != nil {
 		return err
 	}
+	err = yaml.Unmarshal(b, &config)
+	if err != nil {
+		return err
+	}
+	log.Info("configuration file loaded: ", map[string]interface{}{
+		"device_classes": config.DeviceClasses,
+		"socket_name":    config.SocketName,
+		"file_name":      cfgFilePath,
+	})
 	err = lvmd.ValidateDeviceClasses(config.DeviceClasses)
 	if err != nil {
 		return err
@@ -97,7 +118,6 @@ func subMain() error {
 	vgService, notifier := lvmd.NewVGService(manager)
 	proto.RegisterVGServiceServer(grpcServer, vgService)
 	proto.RegisterLVServiceServer(grpcServer, lvmd.NewLVService(manager, notifier))
-	grpc_health_v1.RegisterHealthServer(grpcServer, lvmd.NewHealthService())
 	well.Go(func(ctx context.Context) error {
 		return grpcServer.Serve(lis)
 	})
diff --git a/pkg/topolvm-controller/cmd/run.go b/pkg/topolvm-controller/cmd/run.go
index 5aa81553..f01f7678 100644
--- a/pkg/topolvm-controller/cmd/run.go
+++ b/pkg/topolvm-controller/cmd/run.go
@@ -7,7 +7,9 @@ import (
 	"time"
 
 	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
 	topolvmv1 "github.com/topolvm/topolvm/api/v1"
+	clientwrapper "github.com/topolvm/topolvm/client"
 	"github.com/topolvm/topolvm/controllers"
 	"github.com/topolvm/topolvm/csi"
 	"github.com/topolvm/topolvm/driver"
@@ -36,6 +38,7 @@ func init() {
 	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
 
 	utilruntime.Must(topolvmv1.AddToScheme(scheme))
+	utilruntime.Must(topolvmlegacyv1.AddToScheme(scheme))
 	//+kubebuilder:scaffold:scheme
 }
 
@@ -71,26 +74,23 @@ func subMain() error {
 	if err != nil {
 		return err
 	}
+	client := clientwrapper.NewWrappedClient(mgr.GetClient())
+	apiReader := clientwrapper.NewWrappedReader(mgr.GetAPIReader(), mgr.GetClient().Scheme())
 
 	// register webhook handlers
 	// admissoin.NewDecoder never returns non-nil error
 	dec, _ := admission.NewDecoder(scheme)
 	wh := mgr.GetWebhookServer()
-	wh.Register("/pod/mutate", hook.PodMutator(mgr.GetClient(), mgr.GetAPIReader(), dec))
+	wh.Register("/pod/mutate", hook.PodMutator(client, apiReader, dec))
 
 	// register controllers
-	nodecontroller := &controllers.NodeReconciler{
-		Client:           mgr.GetClient(),
-		SkipNodeFinalize: config.skipNodeFinalize,
-	}
+	nodecontroller := controllers.NewNodeReconciler(client, config.skipNodeFinalize)
 	if err := nodecontroller.SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "Node")
 		return err
 	}
 
-	pvccontroller := &controllers.PersistentVolumeClaimReconciler{
-		Client: mgr.GetClient(),
-	}
+	pvccontroller := controllers.NewPersistentVolumeClaimReconciler(client)
 	if err := pvccontroller.SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "PersistentVolumeClaim")
 		return err
@@ -105,7 +105,7 @@ func subMain() error {
 		defer cancel()
 
 		var drv storagev1.CSIDriver
-		return mgr.GetAPIReader().Get(ctx, types.NamespacedName{Name: topolvm.PluginName}, &drv)
+		return apiReader.Get(ctx, types.NamespacedName{Name: topolvm.GetPluginName()}, &drv)
 	}
 	checker := runners.NewChecker(check, 1*time.Minute)
 	if err := mgr.Add(checker); err != nil {
@@ -117,7 +117,7 @@ func subMain() error {
 	if err != nil {
 		return err
 	}
-	n := k8s.NewNodeService(mgr)
+	n := k8s.NewNodeService(client)
 
 	grpcServer := grpc.NewServer()
 	csi.RegisterIdentityServer(grpcServer, driver.NewIdentityService(checker.Ready))
diff --git a/pkg/topolvm-node/cmd/run.go b/pkg/topolvm-node/cmd/run.go
index 82c9fd9c..fa2e4f77 100644
--- a/pkg/topolvm-node/cmd/run.go
+++ b/pkg/topolvm-node/cmd/run.go
@@ -9,7 +9,9 @@ import (
 
 	"github.com/spf13/viper"
 	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
 	topolvmv1 "github.com/topolvm/topolvm/api/v1"
+	clientwrapper "github.com/topolvm/topolvm/client"
 	"github.com/topolvm/topolvm/controllers"
 	"github.com/topolvm/topolvm/csi"
 	"github.com/topolvm/topolvm/driver"
@@ -38,6 +40,7 @@ func init() {
 	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
 
 	utilruntime.Must(topolvmv1.AddToScheme(scheme))
+	utilruntime.Must(topolvmlegacyv1.AddToScheme(scheme))
 	//+kubebuilder:scaffold:scheme
 }
 
@@ -58,6 +61,8 @@ func subMain() error {
 		setupLog.Error(err, "unable to start manager")
 		return err
 	}
+	client := clientwrapper.NewWrappedClient(mgr.GetClient())
+	apiReader := clientwrapper.NewWrappedReader(mgr.GetAPIReader(), mgr.GetClient().Scheme())
 
 	dialer := &net.Dialer{}
 	dialFunc := func(ctx context.Context, a string) (net.Conn, error) {
@@ -69,12 +74,7 @@ func subMain() error {
 	}
 	defer conn.Close()
 
-	lvcontroller := controllers.NewLogicalVolumeReconciler(
-		mgr.GetClient(),
-		nodename,
-		conn,
-	)
-
+	lvcontroller := controllers.NewLogicalVolumeReconciler(client, nodename, conn)
 	if err := lvcontroller.SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "LogicalVolume")
 		return err
@@ -82,7 +82,7 @@ func subMain() error {
 	//+kubebuilder:scaffold:builder
 
 	// Add health checker to manager
-	checker := runners.NewChecker(checkFunc(conn, mgr.GetAPIReader()), 1*time.Minute)
+	checker := runners.NewChecker(checkFunc(conn, apiReader), 1*time.Minute)
 	if err := mgr.Add(checker); err != nil {
 		return err
 	}
@@ -90,7 +90,7 @@ func subMain() error {
 	// Add metrics exporter to manager.
 	// Note that grpc.ClientConn can be shared with multiple stubs/services.
 	// https://github.com/grpc/grpc-go/tree/master/examples/features/multiplex
-	if err := mgr.Add(runners.NewMetricsExporter(conn, mgr, nodename)); err != nil {
+	if err := mgr.Add(runners.NewMetricsExporter(conn, client, nodename)); err != nil {
 		return err
 	}
 
@@ -132,7 +132,7 @@ func checkFunc(conn *grpc.ClientConn, r client.Reader) func() error {
 		}
 
 		var drv storagev1.CSIDriver
-		return r.Get(ctx, types.NamespacedName{Name: topolvm.PluginName}, &drv)
+		return r.Get(ctx, types.NamespacedName{Name: topolvm.GetPluginName()}, &drv)
 	}
 }
 
diff --git a/pkg/topolvm-scheduler/cmd/root.go b/pkg/topolvm-scheduler/cmd/root.go
index 028d6534..1e9eec4a 100644
--- a/pkg/topolvm-scheduler/cmd/root.go
+++ b/pkg/topolvm-scheduler/cmd/root.go
@@ -42,7 +42,7 @@ The extender implements filter and prioritize verbs.
 
 The filter verb is "predicate" and served at "/predicate" via HTTP.
 It filters out nodes that have less storage capacity than requested.
-The requested capacity is read from "capacity.topolvm.cybozu.com/<device-class>"
+The requested capacity is read from "capacity.topolvm.io/<device-class>"
 resource value.
 
 The prioritize verb is "prioritize" and served at "/prioritize" via HTTP.
diff --git a/runners/metrics_exporter.go b/runners/metrics_exporter.go
index b3449abb..50732b4f 100644
--- a/runners/metrics_exporter.go
+++ b/runners/metrics_exporter.go
@@ -61,7 +61,7 @@ var _ manager.LeaderElectionRunnable = &metricsExporter{}
 
 // NewMetricsExporter creates controller-runtime's manager.Runnable to run
 // a metrics exporter for a node.
-func NewMetricsExporter(conn *grpc.ClientConn, mgr manager.Manager, nodeName string) manager.Runnable {
+func NewMetricsExporter(conn *grpc.ClientConn, client client.Client, nodeName string) manager.Runnable {
 
 	// metrics available under volumegroup subsystem
 	availableBytes := prometheus.NewGaugeVec(prometheus.GaugeOpts{
@@ -120,7 +120,7 @@ func NewMetricsExporter(conn *grpc.ClientConn, mgr manager.Manager, nodeName str
 	metrics.Registry.MustRegister(opAvailableBytes)
 
 	return &metricsExporter{
-		client:         mgr.GetClient(),
+		client:         client,
 		nodeName:       nodeName,
 		vgService:      proto.NewVGServiceClient(conn),
 		availableBytes: availableBytes,
@@ -220,16 +220,16 @@ func (m *metricsExporter) updateNode(ctx context.Context, wc proto.VGService_Wat
 
 		var hasFinalizer bool
 		for _, fin := range node.Finalizers {
-			if fin == topolvm.NodeFinalizer {
+			if fin == topolvm.GetNodeFinalizer() {
 				hasFinalizer = true
 				break
 			}
 		}
 		if !hasFinalizer {
-			node2.Finalizers = append(node2.Finalizers, topolvm.NodeFinalizer)
+			node2.Finalizers = append(node2.Finalizers, topolvm.GetNodeFinalizer())
 		}
 
-		node2.Annotations[topolvm.CapacityKeyPrefix+topolvm.DefaultDeviceClassAnnotationName] = strconv.FormatUint(res.FreeBytes, 10)
+		node2.Annotations[topolvm.GetCapacityKeyPrefix()+topolvm.DefaultDeviceClassAnnotationName] = strconv.FormatUint(res.FreeBytes, 10)
 		for _, item := range res.Items {
 			var freeSize uint64
 			if item.ThinPool != nil {
@@ -237,7 +237,7 @@ func (m *metricsExporter) updateNode(ctx context.Context, wc proto.VGService_Wat
 			} else {
 				freeSize = item.FreeBytes
 			}
-			node2.Annotations[topolvm.CapacityKeyPrefix+item.DeviceClass] = strconv.FormatUint(freeSize, 10)
+			node2.Annotations[topolvm.GetCapacityKeyPrefix()+item.DeviceClass] = strconv.FormatUint(freeSize, 10)
 		}
 		if err := m.client.Patch(ctx, node2, client.MergeFrom(&node)); err != nil {
 			return err
diff --git a/scheduler/predicate.go b/scheduler/predicate.go
index bb5bc570..29ed6eff 100644
--- a/scheduler/predicate.go
+++ b/scheduler/predicate.go
@@ -46,7 +46,7 @@ func filterNodes(nodes corev1.NodeList, requested map[string]int64) ExtenderFilt
 
 func filterNode(node corev1.Node, requested map[string]int64) string {
 	for dc, required := range requested {
-		val, ok := node.Annotations[topolvm.CapacityKeyPrefix+dc]
+		val, ok := node.Annotations[topolvm.GetCapacityKeyPrefix()+dc]
 		if !ok {
 			return "no capacity annotation"
 		}
@@ -64,10 +64,10 @@ func filterNode(node corev1.Node, requested map[string]int64) string {
 func extractRequestedSize(pod *corev1.Pod) map[string]int64 {
 	result := make(map[string]int64)
 	for k, v := range pod.Annotations {
-		if !strings.HasPrefix(k, topolvm.CapacityKeyPrefix) {
+		if !strings.HasPrefix(k, topolvm.GetCapacityKeyPrefix()) {
 			continue
 		}
-		dc := k[len(topolvm.CapacityKeyPrefix):]
+		dc := k[len(topolvm.GetCapacityKeyPrefix()):]
 		capacity, err := strconv.ParseInt(v, 10, 64)
 		if err != nil {
 			continue
diff --git a/scheduler/prioritize.go b/scheduler/prioritize.go
index 41aac2de..a791aefc 100644
--- a/scheduler/prioritize.go
+++ b/scheduler/prioritize.go
@@ -34,8 +34,8 @@ func capacityToScore(capacity uint64, divisor float64) int {
 func scoreNodes(pod *corev1.Pod, nodes []corev1.Node, defaultDivisor float64, divisors map[string]float64) []HostPriority {
 	var dcs []string
 	for k := range pod.Annotations {
-		if strings.HasPrefix(k, topolvm.CapacityKeyPrefix) {
-			dcs = append(dcs, k[len(topolvm.CapacityKeyPrefix):])
+		if strings.HasPrefix(k, topolvm.GetCapacityKeyPrefix()) {
+			dcs = append(dcs, k[len(topolvm.GetCapacityKeyPrefix()):])
 		}
 	}
 	if len(dcs) == 0 {
@@ -62,7 +62,7 @@ func scoreNodes(pod *corev1.Pod, nodes []corev1.Node, defaultDivisor float64, di
 func scoreNode(item corev1.Node, deviceClasses []string, defaultDivisor float64, divisors map[string]float64) int {
 	minScore := math.MaxInt32
 	for _, dc := range deviceClasses {
-		if val, ok := item.Annotations[topolvm.CapacityKeyPrefix+dc]; ok {
+		if val, ok := item.Annotations[topolvm.GetCapacityKeyPrefix()+dc]; ok {
 			capacity, _ := strconv.ParseUint(val, 10, 64)
 			var divisor float64
 			if v, ok := divisors[dc]; ok {


Test patch:
diff --git a/.github/workflows/e2e-k8s-daemonset-lvmd.yaml b/.github/workflows/e2e-k8s-daemonset-lvmd.yaml
index 0c69019c..25fbe8ee 100644
--- a/.github/workflows/e2e-k8s-daemonset-lvmd.yaml
+++ b/.github/workflows/e2e-k8s-daemonset-lvmd.yaml
@@ -13,7 +13,7 @@ on:
 jobs:
   e2e-k8s-daemonset-lvmd:
     name: "e2e-k8s-daemonset-lvmd"
-    runs-on: "ubuntu-18.04"
+    runs-on: "ubuntu-20.04"
     strategy:
       fail-fast: false
       matrix:
diff --git a/.github/workflows/e2e-k8s-workflow.yaml b/.github/workflows/e2e-k8s-workflow.yaml
index 885d05c8..d17c56a2 100644
--- a/.github/workflows/e2e-k8s-workflow.yaml
+++ b/.github/workflows/e2e-k8s-workflow.yaml
@@ -14,7 +14,7 @@ on:
 jobs:
   e2e-k8s:
     name: "e2e-k8s"
-    runs-on: "ubuntu-18.04"
+    runs-on: "ubuntu-20.04"
     strategy:
       fail-fast: false
       matrix:
diff --git a/client/client_test.go b/client/client_test.go
new file mode 100644
index 00000000..c93ca5e5
--- /dev/null
+++ b/client/client_test.go
@@ -0,0 +1,1683 @@
+package client
+
+import (
+	"fmt"
+	"os"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
+	topolvmv1 "github.com/topolvm/topolvm/api/v1"
+	"google.golang.org/grpc/codes"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	corev1 "k8s.io/api/core/v1"
+	apierrs "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+type fakeListOptions struct {
+	f func(opts *client.ListOptions)
+}
+
+func (o *fakeListOptions) ApplyToList(opts *client.ListOptions) {
+	o.f(opts)
+}
+
+const (
+	configmapName      = "test"
+	configmapNamespace = "default"
+)
+
+var (
+	configMapGVK     = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"}
+	configMapListGVK = schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMapList"}
+	configmapKey     = types.NamespacedName{
+		Name:      configmapName,
+		Namespace: configmapNamespace,
+	}
+)
+
+var _ = Describe("client", func() {
+	Context("current env", func() {
+		BeforeEach(func() {
+			os.Setenv("USE_LEGACY", "")
+			err := k8sDelegatedClient.DeleteAllOf(testCtx, &topolvmv1.LogicalVolume{})
+			Expect(err).ShouldNot(HaveOccurred())
+			err = k8sDelegatedClient.DeleteAllOf(testCtx, &topolvmlegacyv1.LogicalVolume{})
+			Expect(err).ShouldNot(HaveOccurred())
+			cm := &corev1.ConfigMap{}
+			cm.Name = configmapName
+			cm.Namespace = configmapNamespace
+			k8sDelegatedClient.Delete(testCtx, cm)
+		})
+
+		Context("wrappedReader", func() {
+			Context("Get", func() {
+				It("standard case", func() {
+					i := 0
+					lv := currentLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					setCurrentLVStatus(lv, i)
+					err = k8sDelegatedClient.Status().Update(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklv := new(topolvmv1.LogicalVolume)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(checklv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+					Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+					Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+					Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+					Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+					Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+					Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+					Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+					Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+					Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+				})
+			})
+
+			Context("List", func() {
+				It("standard case", func() {
+					for i := 0; i < 2; i++ {
+						lv := currentLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						setCurrentLVStatus(lv, i)
+						err = k8sDelegatedClient.Status().Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					lvlist := new(topolvmv1.LogicalVolumeList)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err := c.List(testCtx, lvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(lvlist.Items)).Should(Equal(2))
+					for i, lv := range lvlist.Items {
+						Expect(lv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(lv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(lv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+						Expect(lv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(lv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(lv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(lv.Spec.AccessType).Should(Equal("rw"))
+						Expect(lv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+						Expect(lv.Status.Code).Should(Equal(codes.Unknown))
+						Expect(lv.Status.Message).Should(Equal(codes.Unknown.String()))
+						Expect(lv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					}
+				})
+			})
+		})
+
+		Context("wrappedClient", func() {
+			Context("Get", func() {
+				It("standard case", func() {
+					i := 0
+					lv := currentLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					setCurrentLVStatus(lv, i)
+					err = k8sDelegatedClient.Status().Update(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					Eventually(func(g Gomega) {
+						checklv := new(topolvmv1.LogicalVolume)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(checklv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						g.Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						g.Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+						g.Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						g.Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						g.Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						g.Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+						g.Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+						g.Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+						g.Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+						g.Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					}).Should(Succeed())
+				})
+			})
+
+			Context("List", func() {
+				It("standard case", func() {
+					for i := 0; i < 2; i++ {
+						lv := currentLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						setCurrentLVStatus(lv, i)
+						err = k8sDelegatedClient.Status().Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					Eventually(func(g Gomega) {
+						lvlist := new(topolvmv1.LogicalVolumeList)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err := c.List(testCtx, lvlist)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(len(lvlist.Items)).Should(Equal(2))
+						for i, lv := range lvlist.Items {
+							g.Expect(lv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+							g.Expect(lv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+							g.Expect(lv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+							g.Expect(lv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+							g.Expect(lv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+							g.Expect(lv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+							g.Expect(lv.Spec.AccessType).Should(Equal("rw"))
+							g.Expect(lv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+							g.Expect(lv.Status.Code).Should(Equal(codes.Unknown))
+							g.Expect(lv.Status.Message).Should(Equal(codes.Unknown.String()))
+							g.Expect(lv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						}
+					}).Should(Succeed())
+				})
+			})
+
+			Context("Create", func() {
+				It("standard case", func() {
+					i := 0
+					lv := currentLV(i)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err := c.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklv := new(topolvmv1.LogicalVolume)
+					err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(checklv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+					Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+					Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+					Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+					Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+					Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+				})
+			})
+
+			Context("Delete", func() {
+				It("standard case", func() {
+					i := 0
+					lv := currentLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Delete(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklv := new(topolvmv1.LogicalVolume)
+					err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).Should(HaveOccurred())
+					Expect(apierrs.IsNotFound(err)).Should(BeTrue())
+				})
+			})
+
+			Context("Update", func() {
+				It("standard case", func() {
+					i := 0
+					lv := currentLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					lv.Annotations = ann
+					lv.Spec.Name = fmt.Sprintf("updated-current-%d", i)
+					lv.Spec.NodeName = fmt.Sprintf("updated-node-%d", i)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Update(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklv := new(topolvmv1.LogicalVolume)
+					err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(checklv.Annotations).Should(Equal(ann))
+					Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("updated-current-%d", i)))
+					Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("updated-node-%d", i)))
+					Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+					Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+					Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+				})
+			})
+
+			Context("Patch", func() {
+				It("standard case", func() {
+					i := 0
+					lv := currentLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					lv2 := lv.DeepCopy()
+					lv2.Annotations = ann
+					lv2.Spec.Name = fmt.Sprintf("updated-current-%d", i)
+					lv2.Spec.NodeName = fmt.Sprintf("updated-node-%d", i)
+					c := NewWrappedClient(k8sDelegatedClient)
+					patch := client.MergeFrom(lv)
+					err = c.Patch(testCtx, lv2, patch)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklv := new(topolvmv1.LogicalVolume)
+					err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(checklv.Annotations).Should(Equal(ann))
+					Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("updated-current-%d", i)))
+					Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("updated-node-%d", i)))
+					Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+					Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+					Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+				})
+			})
+
+			Context("DeleteAllOf", func() {
+				It("standard case", func() {
+					for i := 0; i < 2; i++ {
+						lv := currentLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					checklvlist := new(topolvmv1.LogicalVolumeList)
+					err := k8sAPIReader.List(testCtx, checklvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(checklvlist.Items)).Should(Equal(2))
+
+					lv := new(topolvmv1.LogicalVolume)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.DeleteAllOf(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklvlist = new(topolvmv1.LogicalVolumeList)
+					err = k8sAPIReader.List(testCtx, checklvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(checklvlist.Items)).Should(Equal(0))
+				})
+			})
+
+			Context("StatusWriter", func() {
+				Context("Update", func() {
+					It("standard case", func() {
+						i := 0
+						lv := currentLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						setCurrentLVStatus(lv, i)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						checklv := new(topolvmv1.LogicalVolume)
+						err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(checklv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+						Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+						Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+						Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+						Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+						Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					})
+				})
+
+				Context("Patch", func() {
+					It("standard case", func() {
+						i := 0
+						lv := currentLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						lv2 := lv.DeepCopy()
+						setCurrentLVStatus(lv2, i)
+						patch := client.MergeFrom(lv)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Patch(testCtx, lv2, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						checklv := new(topolvmv1.LogicalVolume)
+						err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(checklv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+						Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+						Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+						Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+						Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+						Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					})
+				})
+			})
+		})
+	})
+
+	Context("legacy env", func() {
+		BeforeEach(func() {
+			os.Setenv("USE_LEGACY", "true")
+			err := k8sDelegatedClient.DeleteAllOf(testCtx, &topolvmv1.LogicalVolume{})
+			Expect(err).ShouldNot(HaveOccurred())
+			err = k8sDelegatedClient.DeleteAllOf(testCtx, &topolvmlegacyv1.LogicalVolume{})
+			Expect(err).ShouldNot(HaveOccurred())
+			cm := &corev1.ConfigMap{}
+			cm.Name = configmapName
+			cm.Namespace = configmapNamespace
+			k8sDelegatedClient.Delete(testCtx, cm)
+		})
+
+		Context("wrappedReader", func() {
+			Context("Get", func() {
+				get := func(doCheck bool, f func(*wrappedReader, *topolvmv1.LogicalVolume, string)) {
+					i := 0
+					lv := legacyLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					setLegacyLVStatus(lv, i)
+					err = k8sDelegatedClient.Status().Update(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklv := new(topolvmv1.LogicalVolume)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					r := c.(*wrappedReader)
+					f(r, checklv, lv.Name)
+
+					if doCheck {
+						Expect(checklv.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+						Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+						Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+						Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+						Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+						Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+						Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+						Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+					}
+				}
+
+				It("typedClient", func() {
+					get(true, func(c *wrappedReader, lv *topolvmv1.LogicalVolume, name string) {
+						err := c.Get(testCtx, types.NamespacedName{Name: name}, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					get(true, func(c *wrappedReader, lv *topolvmv1.LogicalVolume, name string) {
+						u := &unstructured.Unstructured{}
+						Expect(c.scheme.Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.Get(testCtx, types.NamespacedName{Name: name}, u)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(c.scheme.Convert(u, lv, nil)).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					get(false, func(c *wrappedReader, lv *topolvmv1.LogicalVolume, name string) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.Get(testCtx, types.NamespacedName{Name: name}, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("non LogicalVolume object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					checkcm := new(corev1.ConfigMap)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.Get(testCtx, configmapKey, checkcm)
+					Expect(err).ShouldNot(HaveOccurred())
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					u := &unstructured.Unstructured{}
+					u.SetGroupVersionKind(configMapGVK)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.Get(testCtx, configmapKey, u)
+					Expect(err).ShouldNot(HaveOccurred())
+				})
+
+				It("non LogicalVolume metav1.PartialObjectMetadata", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					p := &metav1.PartialObjectMetadata{}
+					p.SetGroupVersionKind(configMapGVK)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.Get(testCtx, configmapKey, p)
+					Expect(err).ShouldNot(HaveOccurred())
+				})
+			})
+
+			Context("List", func() {
+				list := func(doCheck bool, f func(*wrappedReader, *topolvmv1.LogicalVolumeList)) {
+					for i := 0; i < 2; i++ {
+						lv := legacyLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						setLegacyLVStatus(lv, i)
+						err = k8sDelegatedClient.Status().Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					lvlist := new(topolvmv1.LogicalVolumeList)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					r := c.(*wrappedReader)
+					f(r, lvlist)
+
+					if doCheck {
+						for i, lv := range lvlist.Items {
+							Expect(lv.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							Expect(lv.Spec.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							Expect(lv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+							Expect(lv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+							Expect(lv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+							Expect(lv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+							Expect(lv.Spec.AccessType).Should(Equal("rw"))
+							Expect(lv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+							Expect(lv.Status.Code).Should(Equal(codes.Unknown))
+							Expect(lv.Status.Message).Should(Equal(codes.Unknown.String()))
+							Expect(lv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						}
+					}
+				}
+
+				It("typedClient", func() {
+					list(true, func(c *wrappedReader, lvlist *topolvmv1.LogicalVolumeList) {
+						err := c.List(testCtx, lvlist)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					list(true, func(c *wrappedReader, lvlist *topolvmv1.LogicalVolumeList) {
+						u := &unstructured.UnstructuredList{}
+						Expect(c.scheme.Convert(lvlist, u, nil)).ShouldNot(HaveOccurred())
+						err := c.List(testCtx, u)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(c.scheme.Convert(u, lvlist, nil)).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					list(false, func(c *wrappedReader, lvlist *topolvmv1.LogicalVolumeList) {
+						p := &metav1.PartialObjectMetadataList{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						err := c.List(testCtx, lvlist)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("option test", func() {
+					var opt1called bool
+					opt1 := &fakeListOptions{
+						f: func(opts *client.ListOptions) {
+							opt1called = true
+						},
+					}
+					var opt2called bool
+					opt2 := &fakeListOptions{
+						f: func(opts *client.ListOptions) {
+							opt2called = true
+						},
+					}
+
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err := c.List(testCtx, new(topolvmv1.LogicalVolumeList), opt1, opt2)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(opt1called).Should(BeTrue())
+					Expect(opt2called).Should(BeTrue())
+				})
+
+				It("non LogicalVolumeList object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					list := new(corev1.ConfigMapList)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.List(testCtx, list)
+					Expect(err).ShouldNot(HaveOccurred())
+					list2 := new(corev1.ConfigMapList)
+					err = k8sAPIReader.List(testCtx, list2)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(list.Items)).Should(Equal(len(list2.Items)))
+				})
+
+				It("non LogicalVolumeList unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					u := &unstructured.UnstructuredList{}
+					u.SetGroupVersionKind(configMapListGVK)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.List(testCtx, u)
+					Expect(err).ShouldNot(HaveOccurred())
+					list := new(corev1.ConfigMapList)
+					Expect(scheme.Convert(u, list, nil)).ShouldNot(HaveOccurred())
+					list2 := new(corev1.ConfigMapList)
+					err = k8sAPIReader.List(testCtx, list2)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(list.Items)).Should(Equal(len(list2.Items)))
+				})
+
+				It("non LogicalVolumeList metav1.PartialObjectMetadata", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					p := &metav1.PartialObjectMetadataList{}
+					p.SetGroupVersionKind(configMapListGVK)
+					c := NewWrappedReader(k8sAPIReader, scheme)
+					err = c.List(testCtx, p)
+					Expect(err).ShouldNot(HaveOccurred())
+					list2 := new(corev1.ConfigMapList)
+					err = k8sAPIReader.List(testCtx, list2)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(p.Items)).Should(Equal(len(list2.Items)))
+				})
+			})
+		})
+
+		Context("wrappedClient", func() {
+			Context("Get", func() {
+				get := func(doCheck bool, f func(Gomega, client.Client, *topolvmv1.LogicalVolume, string)) {
+					i := 0
+					lv := legacyLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					setLegacyLVStatus(lv, i)
+					err = k8sDelegatedClient.Status().Update(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					Eventually(func(g Gomega) {
+						checklv := new(topolvmv1.LogicalVolume)
+						c := NewWrappedClient(k8sDelegatedClient)
+						f(g, c, checklv, lv.Name)
+
+						if doCheck {
+							g.Expect(checklv.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							g.Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							g.Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+							g.Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+							g.Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+							g.Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+							g.Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+							g.Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+							g.Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+							g.Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+							g.Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						}
+					}).Should(Succeed())
+
+				}
+
+				It("typedClient", func() {
+					get(true, func(g Gomega, c client.Client, lv *topolvmv1.LogicalVolume, name string) {
+						err := c.Get(testCtx, types.NamespacedName{Name: name}, lv)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					get(true, func(g Gomega, c client.Client, lv *topolvmv1.LogicalVolume, name string) {
+						u := &unstructured.Unstructured{}
+						g.Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.Get(testCtx, types.NamespacedName{Name: name}, u)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(c.Scheme().Convert(u, lv, nil)).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					get(false, func(g Gomega, c client.Client, lv *topolvmv1.LogicalVolume, name string) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.Get(testCtx, types.NamespacedName{Name: name}, lv)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("non LogicalVolume object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					checkcm := new(corev1.ConfigMap)
+					Eventually(func(g Gomega) {
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Get(testCtx, configmapKey, checkcm)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					}).Should(Succeed())
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					u := &unstructured.Unstructured{}
+					u.SetGroupVersionKind(configMapGVK)
+					Eventually(func(g Gomega) {
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Get(testCtx, configmapKey, u)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					}).Should(Succeed())
+				})
+
+				It("non LogicalVolume metav1.PartialObjectMetadata", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					p := &metav1.PartialObjectMetadata{}
+					p.SetGroupVersionKind(configMapGVK)
+					Eventually(func(g Gomega) {
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Get(testCtx, configmapKey, p)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					}).Should(Succeed())
+				})
+			})
+
+			Context("List", func() {
+				list := func(doCheck bool, f func(Gomega, *wrappedReader, *topolvmv1.LogicalVolumeList)) {
+					for i := 0; i < 2; i++ {
+						lv := legacyLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						setLegacyLVStatus(lv, i)
+						err = k8sDelegatedClient.Status().Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					Eventually(func(g Gomega) {
+						lvlist := new(topolvmv1.LogicalVolumeList)
+						c := NewWrappedReader(k8sAPIReader, scheme)
+						r := c.(*wrappedReader)
+						f(g, r, lvlist)
+
+						if doCheck {
+							for i, lv := range lvlist.Items {
+								Expect(lv.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+								Expect(lv.Spec.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+								Expect(lv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+								Expect(lv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+								Expect(lv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+								Expect(lv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+								Expect(lv.Spec.AccessType).Should(Equal("rw"))
+								Expect(lv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+								Expect(lv.Status.Code).Should(Equal(codes.Unknown))
+								Expect(lv.Status.Message).Should(Equal(codes.Unknown.String()))
+								Expect(lv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+							}
+						}
+					}).Should(Succeed())
+
+				}
+
+				It("typedClient", func() {
+					list(true, func(g Gomega, c *wrappedReader, lvlist *topolvmv1.LogicalVolumeList) {
+						err := c.List(testCtx, lvlist)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					list(true, func(g Gomega, c *wrappedReader, lvlist *topolvmv1.LogicalVolumeList) {
+						u := &unstructured.UnstructuredList{}
+						g.Expect(c.scheme.Convert(lvlist, u, nil)).ShouldNot(HaveOccurred())
+						err := c.List(testCtx, u)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(c.scheme.Convert(u, lvlist, nil)).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					list(false, func(g Gomega, c *wrappedReader, lvlist *topolvmv1.LogicalVolumeList) {
+						p := &metav1.PartialObjectMetadataList{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						err := c.List(testCtx, lvlist)
+						g.Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("option test", func() {
+					var opt1called bool
+					opt1 := &fakeListOptions{
+						f: func(opts *client.ListOptions) {
+							opt1called = true
+						},
+					}
+					var opt2called bool
+					opt2 := &fakeListOptions{
+						f: func(opts *client.ListOptions) {
+							opt2called = true
+						},
+					}
+
+					c := NewWrappedClient(k8sDelegatedClient)
+					err := c.List(testCtx, new(topolvmv1.LogicalVolumeList), opt1, opt2)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(opt1called).Should(BeTrue())
+					Expect(opt2called).Should(BeTrue())
+				})
+
+				It("non LogicalVolumeList object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					Eventually(func(g Gomega) {
+						list := new(corev1.ConfigMapList)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.List(testCtx, list)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						list2 := new(corev1.ConfigMapList)
+						err = k8sAPIReader.List(testCtx, list2)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(len(list.Items)).Should(Equal(len(list2.Items)))
+					}).Should(Succeed())
+				})
+
+				It("non LogicalVolumeList unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					Eventually(func(g Gomega) {
+						u := &unstructured.UnstructuredList{}
+						u.SetGroupVersionKind(configMapGVK)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.List(testCtx, u)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						list2 := new(corev1.ConfigMapList)
+						err = k8sAPIReader.List(testCtx, list2)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(len(u.Items)).Should(Equal(len(list2.Items)))
+					}).Should(Succeed())
+				})
+
+				It("non LogicalVolumeList metav1.PartialObjectMetadata", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					Eventually(func(g Gomega) {
+						p := &metav1.PartialObjectMetadataList{}
+						p.SetGroupVersionKind(configMapGVK)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.List(testCtx, p)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						list2 := new(corev1.ConfigMapList)
+						err = k8sAPIReader.List(testCtx, list2)
+						g.Expect(err).ShouldNot(HaveOccurred())
+						g.Expect(len(p.Items)).Should(Equal(len(list2.Items)))
+					}).Should(Succeed())
+				})
+			})
+
+			Context("Create", func() {
+				create := func(doCheck bool, f func(client.Client, *topolvmv1.LogicalVolume)) {
+					i := 0
+					lv := currentLV(i)
+					c := NewWrappedClient(k8sDelegatedClient)
+					f(c, lv)
+
+					if doCheck {
+						checklv := new(topolvmlegacyv1.LogicalVolume)
+						err := k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(checklv.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("current-%d", i)))
+						Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+						Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+					}
+				}
+
+				It("typedClient", func() {
+					create(true, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						err := c.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					create(true, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						u := &unstructured.Unstructured{}
+						Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					create(false, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.Create(testCtx, p)
+						Expect(err).Should(HaveOccurred())
+					})
+				})
+
+				It("non LogicalVolume object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					c := NewWrappedClient(k8sDelegatedClient)
+					err := c.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					u := &unstructured.Unstructured{}
+					u.SetGroupVersionKind(configMapGVK)
+					u.SetName(configmapName)
+					u.SetNamespace(configmapNamespace)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err := c.Create(testCtx, u)
+					Expect(err).ShouldNot(HaveOccurred())
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+				})
+			})
+
+			Context("Delete", func() {
+				delete := func(f func(client.Client, *topolvmv1.LogicalVolume)) {
+					i := 0
+					lv := legacyLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					c := NewWrappedClient(k8sDelegatedClient)
+					f(c, convertToCurrent(lv))
+
+					checklv := new(topolvmlegacyv1.LogicalVolume)
+					err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).Should(HaveOccurred())
+					Expect(apierrs.IsNotFound(err)).Should(BeTrue())
+				}
+
+				It("typedClient", func() {
+					delete(func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						err := c.Delete(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					delete(func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						u := &unstructured.Unstructured{}
+						Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.Delete(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					delete(func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.Delete(testCtx, p)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("non LogicalVolume object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Delete(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm)
+					Expect(err).Should(HaveOccurred())
+					Expect(apierrs.IsNotFound(err)).Should(BeTrue())
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					u := &unstructured.Unstructured{}
+					u.SetGroupVersionKind(configMapGVK)
+					u.SetName(configmapName)
+					u.SetNamespace(configmapNamespace)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Delete(testCtx, u)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm)
+					Expect(err).Should(HaveOccurred())
+					Expect(apierrs.IsNotFound(err)).Should(BeTrue())
+				})
+
+				It("non LogicalVolume metav1.PartialObjectMetadata", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					p := &metav1.PartialObjectMetadata{}
+					p.SetGroupVersionKind(configMapGVK)
+					p.SetName(configmapName)
+					p.SetNamespace(configmapNamespace)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Delete(testCtx, p)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm)
+					Expect(err).Should(HaveOccurred())
+					Expect(apierrs.IsNotFound(err)).Should(BeTrue())
+				})
+			})
+
+			Context("Update", func() {
+				update := func(doCheck bool, f func(client.Client, *topolvmv1.LogicalVolume)) {
+					i := 0
+					lv := legacyLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					lv2 := convertToCurrent(lv)
+					lv2.Annotations = ann
+					lv2.Spec.Name = fmt.Sprintf("updated-legacy-%d", i)
+					lv2.Spec.NodeName = fmt.Sprintf("updated-node-%d", i)
+					c := NewWrappedClient(k8sDelegatedClient)
+					f(c, lv2)
+
+					if doCheck {
+						checklv := new(topolvmlegacyv1.LogicalVolume)
+						err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(checklv.Annotations).Should(Equal(ann))
+						Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("updated-legacy-%d", i)))
+						Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("updated-node-%d", i)))
+						Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+					}
+				}
+
+				It("typedClient", func() {
+					update(true, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						err := c.Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					update(true, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						u := &unstructured.Unstructured{}
+						Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.Update(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					update(false, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.Update(testCtx, p)
+						Expect(err).Should(HaveOccurred())
+					})
+				})
+
+				It("non LogicalVolume object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					cm2 := cm.DeepCopy()
+					cm2.Annotations = ann
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Update(testCtx, cm2)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					cm3 := new(corev1.ConfigMap)
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm3)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(cm3.Annotations).Should(Equal(ann))
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					cm2 := cm.DeepCopy()
+					cm2.Annotations = ann
+					u := &unstructured.Unstructured{}
+					Expect(scheme.Convert(cm2, u, nil)).ShouldNot(HaveOccurred())
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Update(testCtx, u)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					cm3 := new(corev1.ConfigMap)
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm3)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(cm3.Annotations).Should(Equal(ann))
+				})
+			})
+
+			Context("Patch", func() {
+				patch := func(doCheck bool, f func(client.Client, *topolvmv1.LogicalVolume, client.Patch)) {
+					i := 0
+					lv := legacyLV(i)
+					err := k8sDelegatedClient.Create(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					lv2 := convertToCurrent(lv)
+					lv2.TypeMeta.APIVersion = topolvmlegacyv1.GroupVersion.String()
+					lv3 := lv2.DeepCopy()
+					lv3.Annotations = ann
+					lv3.Spec.Name = fmt.Sprintf("updated-legacy-%d", i)
+					lv3.Spec.NodeName = fmt.Sprintf("updated-node-%d", i)
+					patch := client.MergeFrom(lv2)
+					c := NewWrappedClient(k8sDelegatedClient)
+					f(c, lv3, patch)
+
+					checklv := new(topolvmlegacyv1.LogicalVolume)
+					err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(checklv.Annotations).Should(Equal(ann))
+					if doCheck {
+						Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("updated-legacy-%d", i)))
+						Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("updated-node-%d", i)))
+						Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+						Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+						Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+					}
+				}
+
+				It("typedClient", func() {
+					patch(true, func(c client.Client, lv *topolvmv1.LogicalVolume, patch client.Patch) {
+						err := c.Patch(testCtx, lv, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					patch(true, func(c client.Client, lv *topolvmv1.LogicalVolume, patch client.Patch) {
+						u := &unstructured.Unstructured{}
+						Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.Patch(testCtx, lv, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					patch(false, func(c client.Client, lv *topolvmv1.LogicalVolume, patch client.Patch) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.Patch(testCtx, p, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("non LogicalVolume object", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					cm2 := cm.DeepCopy()
+					cm3 := cm2.DeepCopy()
+					cm3.Annotations = ann
+					patch := client.MergeFrom(cm2)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Patch(testCtx, cm3, patch)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					cm4 := new(corev1.ConfigMap)
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm4)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(cm4.Annotations).Should(Equal(ann))
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					cm2 := cm.DeepCopy()
+					cm3 := cm2.DeepCopy()
+					cm3.Annotations = ann
+					u := &unstructured.Unstructured{}
+					Expect(scheme.Convert(cm3, u, nil)).ShouldNot(HaveOccurred())
+					patch := client.MergeFrom(cm2)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Patch(testCtx, u, patch)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					cm4 := new(corev1.ConfigMap)
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm4)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(cm4.Annotations).Should(Equal(ann))
+				})
+
+				It("non LogicalVolume metav1.PartialObjectMetadata", func() {
+					cm := new(corev1.ConfigMap)
+					cm.Name = configmapName
+					cm.Namespace = configmapNamespace
+					err := k8sDelegatedClient.Create(testCtx, cm)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					ann := map[string]string{"foo": "bar"}
+					p := &metav1.PartialObjectMetadata{}
+					p.SetGroupVersionKind(configMapGVK)
+					p.SetName(configmapName)
+					p.SetNamespace(configmapNamespace)
+					p.SetAnnotations(ann)
+					patch := client.MergeFrom(cm)
+					p.SetAnnotations(ann)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.Patch(testCtx, p, patch)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					cm2 := new(corev1.ConfigMap)
+					err = k8sAPIReader.Get(testCtx, configmapKey, cm2)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(cm2.Annotations).Should(Equal(ann))
+				})
+			})
+
+			Context("DeleteAllOf", func() {
+				deleteAllOf := func(f func(client.Client, *topolvmv1.LogicalVolume)) {
+					for i := 0; i < 2; i++ {
+						lv := legacyLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					checklvlist := new(topolvmlegacyv1.LogicalVolumeList)
+					err := k8sAPIReader.List(testCtx, checklvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(checklvlist.Items)).Should(Equal(2))
+
+					lv := new(topolvmv1.LogicalVolume)
+					c := NewWrappedClient(k8sDelegatedClient)
+					f(c, lv)
+
+					checklvlist = new(topolvmlegacyv1.LogicalVolumeList)
+					err = k8sAPIReader.List(testCtx, checklvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(checklvlist.Items)).Should(Equal(0))
+				}
+
+				It("typedClient", func() {
+					deleteAllOf(func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						err := c.DeleteAllOf(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("unstructured.Unstructured", func() {
+					deleteAllOf(func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						u := &unstructured.Unstructured{}
+						Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+						err := c.DeleteAllOf(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("metav1.PartialObjectMetadata", func() {
+					deleteAllOf(func(c client.Client, lv *topolvmv1.LogicalVolume) {
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+						lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+						err := c.DeleteAllOf(testCtx, p)
+						Expect(err).ShouldNot(HaveOccurred())
+					})
+				})
+
+				It("typedClient", func() {
+					for i := 0; i < 2; i++ {
+						lv := legacyLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+					}
+
+					checklvlist := new(topolvmlegacyv1.LogicalVolumeList)
+					err := k8sAPIReader.List(testCtx, checklvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(checklvlist.Items)).Should(Equal(2))
+
+					lv := new(topolvmv1.LogicalVolume)
+					c := NewWrappedClient(k8sDelegatedClient)
+					err = c.DeleteAllOf(testCtx, lv)
+					Expect(err).ShouldNot(HaveOccurred())
+
+					checklvlist = new(topolvmlegacyv1.LogicalVolumeList)
+					err = k8sAPIReader.List(testCtx, checklvlist)
+					Expect(err).ShouldNot(HaveOccurred())
+					Expect(len(checklvlist.Items)).Should(Equal(0))
+				})
+
+				It("non LogicalVolume object", func() {
+					Skip("If you found resources other than LogicalVolume resource what DeleteAllOf can be executed in the envtest environment, please write test cases for these resources.")
+				})
+
+				It("non LogicalVolume unstructured.Unstructured", func() {
+					Skip("If you found resources other than LogicalVolume resource what DeleteAllOf can be executed in the envtest environment, please write test cases for these resources.")
+				})
+
+				It("non LogicalVolume metav1.PartialObjectMetadata", func() {
+					Skip("If you found resources other than LogicalVolume resource what DeleteAllOf can be executed in the envtest environment, please write test cases for these resources.")
+				})
+			})
+
+			Context("StatusWriter", func() {
+				BeforeEach(func() {
+					svc := &corev1.Service{}
+					svc.Name = configmapName
+					svc.Namespace = configmapNamespace
+					k8sDelegatedClient.Delete(testCtx, svc)
+				})
+
+				Context("Update", func() {
+					update := func(doCheck bool, f func(client.Client, *topolvmv1.LogicalVolume)) {
+						i := 0
+						lv := legacyLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						lv2 := convertToCurrent(lv)
+						setCurrentLVStatus(lv2, i)
+						c := NewWrappedClient(k8sDelegatedClient)
+						f(c, lv2)
+
+						if doCheck {
+							checklv := new(topolvmlegacyv1.LogicalVolume)
+							err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+							Expect(err).ShouldNot(HaveOccurred())
+							Expect(checklv.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+							Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+							Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+							Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+							Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+							Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+							Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+							Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+							Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						}
+					}
+
+					It("typedClient", func() {
+						update(true, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+							err := c.Status().Update(testCtx, lv)
+							Expect(err).ShouldNot(HaveOccurred())
+						})
+					})
+
+					It("unstructured.Unstructured", func() {
+						update(true, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+							u := &unstructured.Unstructured{}
+							Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+							err := c.Status().Update(testCtx, lv)
+							Expect(err).ShouldNot(HaveOccurred())
+						})
+					})
+
+					It("metav1.PartialObjectMetadata", func() {
+						update(false, func(c client.Client, lv *topolvmv1.LogicalVolume) {
+							p := &metav1.PartialObjectMetadata{}
+							p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+							lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+							err := c.Status().Update(testCtx, p)
+							Expect(err).Should(HaveOccurred())
+						})
+					})
+
+					It("non LogicalVolume object", func() {
+						svc := new(corev1.Service)
+						svc.Name = configmapName
+						svc.Namespace = configmapNamespace
+						svc.Spec = corev1.ServiceSpec{
+							Selector: map[string]string{"app": "MyApp"},
+							Ports: []corev1.ServicePort{
+								{
+									Protocol: corev1.ProtocolTCP,
+									Port:     80,
+								},
+							},
+						}
+						err := k8sDelegatedClient.Create(testCtx, svc)
+						Expect(err).ShouldNot(HaveOccurred())
+						defer func() {
+							err := k8sDelegatedClient.Delete(testCtx, svc)
+							Expect(err).ShouldNot(HaveOccurred())
+						}()
+
+						svc2 := svc.DeepCopy()
+						lbstatus := corev1.LoadBalancerStatus{
+							Ingress: []corev1.LoadBalancerIngress{
+								{
+									Hostname: "example.com",
+								},
+							},
+						}
+						svc2.Status.LoadBalancer = lbstatus
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Update(testCtx, svc2)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc3 := new(corev1.Service)
+						err = k8sAPIReader.Get(testCtx, configmapKey, svc3)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(svc3.Status.LoadBalancer).Should(Equal(lbstatus))
+					})
+
+					It("non LogicalVolume unstructured.Unstructured", func() {
+						svc := new(corev1.Service)
+						svc.Name = configmapName
+						svc.Namespace = configmapNamespace
+						svc.Spec = corev1.ServiceSpec{
+							Selector: map[string]string{"app": "MyApp"},
+							Ports: []corev1.ServicePort{
+								{
+									Protocol: corev1.ProtocolTCP,
+									Port:     80,
+								},
+							},
+						}
+						err := k8sDelegatedClient.Create(testCtx, svc)
+						Expect(err).ShouldNot(HaveOccurred())
+						defer func() {
+							err := k8sDelegatedClient.Delete(testCtx, svc)
+							Expect(err).ShouldNot(HaveOccurred())
+						}()
+
+						svc2 := svc.DeepCopy()
+						lbstatus := corev1.LoadBalancerStatus{
+							Ingress: []corev1.LoadBalancerIngress{
+								{
+									Hostname: "example.com",
+								},
+							},
+						}
+						svc2.Status.LoadBalancer = lbstatus
+						u := &unstructured.Unstructured{}
+						u.SetGroupVersionKind(configMapGVK)
+						Expect(scheme.Convert(svc2, u, nil)).ShouldNot(HaveOccurred())
+
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Update(testCtx, u)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc3 := new(corev1.Service)
+						err = k8sAPIReader.Get(testCtx, configmapKey, svc3)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(svc3.Status.LoadBalancer).Should(Equal(lbstatus))
+					})
+				})
+
+				Context("Patch", func() {
+					patch := func(doCheck bool, f func(client.Client, *topolvmv1.LogicalVolume, client.Patch)) {
+						i := 0
+						lv := legacyLV(i)
+						err := k8sDelegatedClient.Create(testCtx, lv)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						lv2 := convertToCurrent(lv)
+						lv2.TypeMeta.APIVersion = topolvmlegacyv1.GroupVersion.String()
+						lv3 := lv2.DeepCopy()
+						setCurrentLVStatus(lv3, i)
+						patch := client.MergeFrom(lv2)
+						c := NewWrappedClient(k8sDelegatedClient)
+						f(c, lv3, patch)
+
+						checklv := new(topolvmlegacyv1.LogicalVolume)
+						err = k8sAPIReader.Get(testCtx, types.NamespacedName{Name: lv.Name}, checklv)
+						Expect(err).ShouldNot(HaveOccurred())
+						if doCheck {
+							Expect(checklv.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							Expect(checklv.Spec.Name).Should(Equal(fmt.Sprintf("legacy-%d", i)))
+							Expect(checklv.Spec.NodeName).Should(Equal(fmt.Sprintf("node-%d", i)))
+							Expect(checklv.Spec.DeviceClass).Should(Equal(topolvm.DefaultDeviceClassName))
+							Expect(checklv.Spec.Size.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+							Expect(checklv.Spec.Source).Should(Equal(fmt.Sprintf("source-%d", i)))
+							Expect(checklv.Spec.AccessType).Should(Equal("rw"))
+							Expect(checklv.Status.VolumeID).Should(Equal(fmt.Sprintf("volume-%d", i)))
+							Expect(checklv.Status.Code).Should(Equal(codes.Unknown))
+							Expect(checklv.Status.Message).Should(Equal(codes.Unknown.String()))
+							Expect(checklv.Status.CurrentSize.Value()).Should(Equal(resource.NewQuantity(1<<30, resource.BinarySI).Value()))
+						}
+					}
+
+					It("typedClient", func() {
+						patch(true, func(c client.Client, lv *topolvmv1.LogicalVolume, patch client.Patch) {
+							err := c.Status().Patch(testCtx, lv, patch)
+							Expect(err).ShouldNot(HaveOccurred())
+						})
+					})
+
+					It("unstructured.Unstructured", func() {
+						patch(true, func(c client.Client, lv *topolvmv1.LogicalVolume, patch client.Patch) {
+							u := &unstructured.Unstructured{}
+							Expect(c.Scheme().Convert(lv, u, nil)).ShouldNot(HaveOccurred())
+							err := c.Status().Patch(testCtx, lv, patch)
+							Expect(err).ShouldNot(HaveOccurred())
+						})
+					})
+
+					It("metav1.PartialObjectMetadata", func() {
+						patch(false, func(c client.Client, lv *topolvmv1.LogicalVolume, patch client.Patch) {
+							p := &metav1.PartialObjectMetadata{}
+							p.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+							lv.ObjectMeta.DeepCopyInto(&p.ObjectMeta)
+							err := c.Status().Patch(testCtx, p, patch)
+							Expect(err).ShouldNot(HaveOccurred())
+						})
+					})
+
+					It("non LogicalVolume object", func() {
+						svc := new(corev1.Service)
+						svc.Name = configmapName
+						svc.Namespace = configmapNamespace
+						svc.Spec = corev1.ServiceSpec{
+							Selector: map[string]string{"app": "MyApp"},
+							Ports: []corev1.ServicePort{
+								{
+									Protocol: corev1.ProtocolTCP,
+									Port:     80,
+								},
+							},
+						}
+						err := k8sDelegatedClient.Create(testCtx, svc)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc2 := svc.DeepCopy()
+						svc3 := svc2.DeepCopy()
+						lbstatus := corev1.LoadBalancerStatus{
+							Ingress: []corev1.LoadBalancerIngress{
+								{
+									Hostname: "example.com",
+								},
+							},
+						}
+						svc3.Status.LoadBalancer = lbstatus
+						patch := client.MergeFrom(svc2)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Patch(testCtx, svc3, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc4 := new(corev1.Service)
+						err = k8sAPIReader.Get(testCtx, configmapKey, svc4)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(svc4.Status.LoadBalancer).Should(Equal(lbstatus))
+					})
+
+					It("non LogicalVolume unstructured.Unstructured", func() {
+						svc := new(corev1.Service)
+						svc.Name = configmapName
+						svc.Namespace = configmapNamespace
+						svc.Spec = corev1.ServiceSpec{
+							Selector: map[string]string{"app": "MyApp"},
+							Ports: []corev1.ServicePort{
+								{
+									Protocol: corev1.ProtocolTCP,
+									Port:     80,
+								},
+							},
+						}
+						err := k8sDelegatedClient.Create(testCtx, svc)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc2 := svc.DeepCopy()
+						svc3 := svc2.DeepCopy()
+						lbstatus := corev1.LoadBalancerStatus{
+							Ingress: []corev1.LoadBalancerIngress{
+								{
+									Hostname: "example.com",
+								},
+							},
+						}
+						svc3.Status.LoadBalancer = lbstatus
+						patch := client.MergeFrom(svc2)
+						u := &unstructured.Unstructured{}
+						Expect(scheme.Convert(svc3, u, nil)).ShouldNot(HaveOccurred())
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Patch(testCtx, u, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc4 := new(corev1.Service)
+						err = k8sAPIReader.Get(testCtx, configmapKey, svc4)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(svc4.Status.LoadBalancer).Should(Equal(lbstatus))
+					})
+
+					It("non LogicalVolume metav1.PartialObjectMetadata", func() {
+						svc := new(corev1.Service)
+						svc.Name = configmapName
+						svc.Namespace = configmapNamespace
+						svc.Spec = corev1.ServiceSpec{
+							Selector: map[string]string{"app": "MyApp"},
+							Ports: []corev1.ServicePort{
+								{
+									Protocol: corev1.ProtocolTCP,
+									Port:     80,
+								},
+							},
+						}
+						err := k8sDelegatedClient.Create(testCtx, svc)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						ann := map[string]string{"foo": "bar"}
+						svc2 := svc.DeepCopy()
+						svc2.Spec = corev1.ServiceSpec{}
+						svc2.Status = corev1.ServiceStatus{}
+						patch := client.MergeFrom(svc2)
+						p := &metav1.PartialObjectMetadata{}
+						p.SetGroupVersionKind(svc2.GroupVersionKind().GroupVersion().WithKind("Service"))
+						p.SetName(configmapName)
+						p.SetNamespace(configmapNamespace)
+						p.SetAnnotations(ann)
+						c := NewWrappedClient(k8sDelegatedClient)
+						err = c.Status().Patch(testCtx, p, patch)
+						Expect(err).ShouldNot(HaveOccurred())
+
+						svc4 := new(corev1.Service)
+						err = k8sAPIReader.Get(testCtx, configmapKey, svc4)
+						Expect(err).ShouldNot(HaveOccurred())
+						Expect(svc4.Annotations).Should(Equal(ann))
+					})
+				})
+			})
+		})
+	})
+})
diff --git a/client/suite_test.go b/client/suite_test.go
new file mode 100644
index 00000000..bd23c66f
--- /dev/null
+++ b/client/suite_test.go
@@ -0,0 +1,177 @@
+package client
+
+import (
+	"context"
+	"fmt"
+	"path/filepath"
+	"testing"
+	"time"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/topolvm/topolvm"
+	topolvmlegacyv1 "github.com/topolvm/topolvm/api/legacy/v1"
+	topolvmv1 "github.com/topolvm/topolvm/api/v1"
+	testingutil "github.com/topolvm/topolvm/util/testing"
+	"google.golang.org/grpc/codes"
+
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/conversion"
+	"k8s.io/apimachinery/pkg/runtime"
+	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/cluster"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+	//+kubebuilder:scaffold:imports
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var scheme = runtime.NewScheme()
+
+var k8sDelegatedClient client.Client
+var k8sAPIReader client.Reader
+var k8sCache cache.Cache
+
+var testEnv *envtest.Environment
+var testCtx, testCancel = context.WithCancel(context.Background())
+
+func TestAPIs(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	RegisterFailHandler(Fail)
+
+	SetDefaultEventuallyPollingInterval(time.Second)
+	SetDefaultEventuallyTimeout(time.Minute)
+
+	RunSpecsWithDefaultAndCustomReporters(t,
+		"Controller Suite",
+		[]Reporter{printer.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func() {
+	logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+
+	By("bootstrapping test environment")
+	testEnv = &envtest.Environment{
+		CRDDirectoryPaths:     []string{filepath.Join("..", "config", "crd", "bases")},
+		ErrorIfCRDPathMissing: true,
+	}
+
+	cfg, err := testEnv.Start()
+	Expect(err).NotTo(HaveOccurred())
+	Expect(cfg).NotTo(BeNil())
+
+	err = clientgoscheme.AddToScheme(scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	err = topolvmv1.AddToScheme(scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	err = topolvmlegacyv1.AddToScheme(scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	//+kubebuilder:scaffold:scheme
+
+	c, err := cluster.New(cfg, func(clusterOptions *cluster.Options) {
+		clusterOptions.Scheme = scheme
+	})
+	Expect(err).NotTo(HaveOccurred())
+	k8sDelegatedClient = c.GetClient()
+	k8sCache = c.GetCache()
+	k8sAPIReader = c.GetAPIReader()
+
+	go func() {
+		err := k8sCache.Start(testCtx)
+		Expect(err).NotTo(HaveOccurred())
+	}()
+	Expect(k8sCache.WaitForCacheSync(testCtx)).ToNot(BeFalse())
+
+	scheme.Converter().WithConversions(conversion.NewConversionFuncs())
+}, 60)
+
+var _ = AfterSuite(func() {
+	By("tearing down the test environment")
+	testCancel()
+	err := testEnv.Stop()
+	Expect(err).NotTo(HaveOccurred())
+})
+
+func currentLV(i int) *topolvmv1.LogicalVolume {
+	return &topolvmv1.LogicalVolume{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: fmt.Sprintf("current-%d", i),
+		},
+		Spec: topolvmv1.LogicalVolumeSpec{
+			Name:        fmt.Sprintf("current-%d", i),
+			NodeName:    fmt.Sprintf("node-%d", i),
+			DeviceClass: topolvm.DefaultDeviceClassName,
+			Size:        *resource.NewQuantity(1<<30, resource.BinarySI),
+			Source:      fmt.Sprintf("source-%d", i),
+			AccessType:  "rw",
+		},
+		Status: topolvmv1.LogicalVolumeStatus{
+			VolumeID:    fmt.Sprintf("volume-%d", i),
+			Code:        codes.Unknown,
+			Message:     codes.Unknown.String(),
+			CurrentSize: resource.NewQuantity(1<<30, resource.BinarySI),
+		},
+	}
+}
+
+func legacyLV(i int) *topolvmlegacyv1.LogicalVolume {
+	return &topolvmlegacyv1.LogicalVolume{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: fmt.Sprintf("legacy-%d", i),
+		},
+		Spec: topolvmlegacyv1.LogicalVolumeSpec{
+			Name:        fmt.Sprintf("legacy-%d", i),
+			NodeName:    fmt.Sprintf("node-%d", i),
+			DeviceClass: topolvm.DefaultDeviceClassName,
+			Size:        *resource.NewQuantity(1<<30, resource.BinarySI),
+			Source:      fmt.Sprintf("source-%d", i),
+			AccessType:  "rw",
+		},
+		Status: topolvmlegacyv1.LogicalVolumeStatus{
+			VolumeID:    fmt.Sprintf("volume-%d", i),
+			Code:        codes.Unknown,
+			Message:     codes.Unknown.String(),
+			CurrentSize: resource.NewQuantity(1<<30, resource.BinarySI),
+		},
+	}
+}
+
+func setCurrentLVStatus(lv *topolvmv1.LogicalVolume, i int) {
+	lv.Status = topolvmv1.LogicalVolumeStatus{
+		VolumeID:    fmt.Sprintf("volume-%d", i),
+		Code:        codes.Unknown,
+		Message:     codes.Unknown.String(),
+		CurrentSize: resource.NewQuantity(1<<30, resource.BinarySI),
+	}
+}
+
+func setLegacyLVStatus(lv *topolvmlegacyv1.LogicalVolume, i int) {
+	lv.Status = topolvmlegacyv1.LogicalVolumeStatus{
+		VolumeID:    fmt.Sprintf("volume-%d", i),
+		Code:        codes.Unknown,
+		Message:     codes.Unknown.String(),
+		CurrentSize: resource.NewQuantity(1<<30, resource.BinarySI),
+	}
+}
+
+func convertToCurrent(lv *topolvmlegacyv1.LogicalVolume) *topolvmv1.LogicalVolume {
+	u := &unstructured.Unstructured{}
+	err := k8sDelegatedClient.Scheme().Convert(lv, u, nil)
+	Expect(err).ShouldNot(HaveOccurred())
+	u.SetGroupVersionKind(topolvmv1.GroupVersion.WithKind(kind))
+	current := new(topolvmv1.LogicalVolume)
+	err = k8sDelegatedClient.Scheme().Convert(u, current, nil)
+	Expect(err).ShouldNot(HaveOccurred())
+	return current
+}
diff --git a/constants_test.go b/constants_test.go
new file mode 100644
index 00000000..f5373bb1
--- /dev/null
+++ b/constants_test.go
@@ -0,0 +1,137 @@
+package topolvm
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+	"testing"
+
+	testingutil "github.com/topolvm/topolvm/util/testing"
+)
+
+func TestUseLegacy(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	tests := []struct {
+		name     string
+		envval   string
+		expected bool
+	}{
+		{
+			name:     "return false if USE_LEGACY env value is empty",
+			envval:   "",
+			expected: false,
+		},
+		{
+			name:     "return true if USE_LEGACY env value is not empty",
+			envval:   "true",
+			expected: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			os.Setenv("USE_LEGACY", tt.envval)
+			if UseLegacy() != tt.expected {
+				t.Fatalf("return value is not %s", strconv.FormatBool(tt.expected))
+			}
+		})
+	}
+}
+
+func TestGetPluginName(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	tests := []struct {
+		name     string
+		envval   string
+		expected string
+	}{
+		{
+			name:     fmt.Sprintf("return %q if USE_LEGACY env value is empty", pluginName),
+			envval:   "",
+			expected: pluginName,
+		},
+		{
+			name:     fmt.Sprintf("return %q if USE_LEGACY env value is not empty", legacyPluginName),
+			envval:   "true",
+			expected: legacyPluginName,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			os.Setenv("USE_LEGACY", tt.envval)
+			if GetPluginName() != tt.expected {
+				t.Fatalf("return value is not %s", tt.expected)
+			}
+		})
+	}
+}
+
+func TestGetCapacityKeyPrefix(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetCapacityKeyPrefix)
+}
+
+func TestGetCapacityResource(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, func() string {
+		return string(GetCapacityResource())
+	})
+}
+
+func TestGetTopologyNodeKey(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetTopologyNodeKey)
+}
+
+func TestGetDeviceClassKey(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetDeviceClassKey)
+}
+
+func TestGetResizeRequestedAtKey(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetResizeRequestedAtKey)
+}
+
+func TestGetLogicalVolumeFinalizer(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetLogicalVolumeFinalizer)
+}
+
+func TestGetNodeFinalizer(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetNodeFinalizer)
+}
+
+func TestGetPVCFinalizer(t *testing.T) {
+	testingutil.DoEnvCheck(t)
+	doContainTest(t, GetPVCFinalizer)
+}
+
+func doContainTest(t *testing.T, f func() string) {
+	tests := []struct {
+		name      string
+		envval    string
+		contained string
+	}{
+		{
+			name:      fmt.Sprintf("return strings containing %q if USE_LEGACY env value is empty", pluginName),
+			envval:    "",
+			contained: pluginName,
+		},
+		{
+			name:      fmt.Sprintf("return strings containing %q if USE_LEGACY env value is not empty", legacyPluginName),
+			envval:    "true",
+			contained: legacyPluginName,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			os.Setenv("USE_LEGACY", tt.envval)
+			val := f()
+			if !strings.Contains(val, tt.contained) {
+				t.Fatalf("return value %q does not contain strings: %s", val, tt.contained)
+			}
+		})
+	}
+}
diff --git a/controllers/persistentvolumeclaim_controller_test.go b/controllers/persistentvolumeclaim_controller_test.go
index f6240a7d..2e04c6d2 100644
--- a/controllers/persistentvolumeclaim_controller_test.go
+++ b/controllers/persistentvolumeclaim_controller_test.go
@@ -31,9 +31,7 @@ var _ = Describe("PersistentVolumeClaimController controller", func() {
 		})
 		Expect(err).ToNot(HaveOccurred())
 
-		reconciler := PersistentVolumeClaimReconciler{
-			Client: k8sClient,
-		}
+		reconciler := NewPersistentVolumeClaimReconciler(k8sClient)
 		err = reconciler.SetupWithManager(mgr)
 		Expect(err).NotTo(HaveOccurred())
 
@@ -78,7 +76,7 @@ func newPVCWithFinalizer() *corev1.PersistentVolumeClaim {
 		ObjectMeta: metav1.ObjectMeta{
 			Name:       "sample",
 			Namespace:  "test",
-			Finalizers: []string{topolvm.PVCFinalizer, "topolvm.io/finalizer1", topolvm.PVCFinalizer, topolvm.PVCFinalizer, "topolvm.io/finalizer2", topolvm.PVCFinalizer},
+			Finalizers: []string{topolvm.GetPVCFinalizer(), "topolvm.io/finalizer1", topolvm.GetPVCFinalizer(), topolvm.GetPVCFinalizer(), "topolvm.io/finalizer2", topolvm.GetPVCFinalizer()},
 		},
 		Spec: corev1.PersistentVolumeClaimSpec{
 			AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
diff --git a/e2e/Makefile b/e2e/Makefile
index 3e68522d..e1812afc 100644
--- a/e2e/Makefile
+++ b/e2e/Makefile
@@ -72,8 +72,8 @@ topolvm.img: $(GO_FILES)
 .PHONY: prepare-namespace
 prepare-namespace:
 	$(KUBECTL) create namespace topolvm-system
-	$(KUBECTL) label namespace topolvm-system topolvm.cybozu.com/webhook=ignore
-	$(KUBECTL) label namespace kube-system topolvm.cybozu.com/webhook=ignore
+	$(KUBECTL) label namespace topolvm-system topolvm.io/webhook=ignore
+	$(KUBECTL) label namespace kube-system topolvm.io/webhook=ignore
 
 .PHONY: apply-certmanager-crds
 apply-certmanager-crds:
@@ -140,7 +140,6 @@ start-lvmd:
 	done
 
 	for i in $$(seq 3); do \
-		$(SUDO) systemctl reset-failed lvmd$$i.service || true; \
 		$(SUDO) systemd-run --unit=lvmd$$i.service $(shell pwd)/build/lvmd --config=$(shell pwd)/lvmd$$i.yaml; \
 	done
 
@@ -173,10 +172,12 @@ create-cluster: topolvm.img
 	$(HELM) repo update
 	$(HELM) dependency build ../charts/topolvm/
 	$(HELM) install --namespace=topolvm-system topolvm ../charts/topolvm/ -f $(HELM_VALUES_FILE) --set "controller.nodeSelector.kubernetes\.io/hostname"=$(KIND_CLUSTER_NAME)-worker $(HELM_SKIP_NODE_FINALIZE_FLAG)
+	$(KUBECTL) wait --for=condition=available --timeout=120s -n topolvm-system deployments/topolvm-controller
+	$(KUBECTL) wait --for=condition=ready --timeout=120s -n topolvm-system -l="app.kubernetes.io/component=controller,app.kubernetes.io/name=topolvm" pod
+	$(KUBECTL) wait --for=condition=ready --timeout=120s -n topolvm-system certificate/topolvm-mutatingwebhook
 
 .PHONY: test
 test: create-cluster
-	# deploy snapshot-controller
 	$(KUBECTL) apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$(EXTERNAL_SNAPSHOTTER_VERSION)/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
 	$(KUBECTL) apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$(EXTERNAL_SNAPSHOTTER_VERSION)/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
 	$(KUBECTL) apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$(EXTERNAL_SNAPSHOTTER_VERSION)/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
@@ -284,7 +285,9 @@ daemonset-lvmd/test: topolvm.img
 	$(HELM) repo update
 	$(HELM) dependency build ../charts/topolvm/
 	$(HELM) install --namespace=topolvm-system topolvm ../charts/topolvm/ -f $(HELM_VALUES_FILE_LVMD) $(HELM_SKIP_NODE_FINALIZE_FLAG)
-	# deploy snapshot-controller
+	$(KUBECTL) wait --for=condition=available --timeout=120s -n topolvm-system deployments/topolvm-controller
+	$(KUBECTL) wait --for=condition=ready --timeout=120s -n topolvm-system -l="app.kubernetes.io/component=controller,app.kubernetes.io/name=topolvm" pod
+	$(KUBECTL) wait --for=condition=ready --timeout=120s -n topolvm-system certificate/topolvm-mutatingwebhook
 	$(KUBECTL) apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$(EXTERNAL_SNAPSHOTTER_VERSION)/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
 	$(KUBECTL) apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$(EXTERNAL_SNAPSHOTTER_VERSION)/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
 	$(KUBECTL) apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$(EXTERNAL_SNAPSHOTTER_VERSION)/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
diff --git a/e2e/capacity_test.go b/e2e/capacity_test.go
index 33be503f..1f06354c 100644
--- a/e2e/capacity_test.go
+++ b/e2e/capacity_test.go
@@ -50,7 +50,7 @@ func testStorageCapacity() {
 		err = json.Unmarshal(result, &node)
 		Expect(err).ShouldNot(HaveOccurred())
 		size := func() string {
-			sizeStr, exists := node.Annotations[topolvm.CapacityKeyPrefix+"00default"]
+			sizeStr, exists := node.Annotations[topolvm.GetCapacityKeyPrefix()+"00default"]
 			Expect(exists).Should(BeTrue(), "size is not found")
 			size, err := strconv.Atoi(sizeStr)
 			Expect(err).ShouldNot(HaveOccurred())
@@ -140,7 +140,7 @@ func testStorageCapacity() {
 		err = json.Unmarshal(result, &node)
 		Expect(err).ShouldNot(HaveOccurred())
 		size = func() string {
-			sizeStr, exists := node.Annotations[topolvm.CapacityKeyPrefix+"hdd1"]
+			sizeStr, exists := node.Annotations[topolvm.GetCapacityKeyPrefix()+"hdd1"]
 			if !exists {
 				Expect(errors.New("size is not found")).ShouldNot(HaveOccurred())
 			}
diff --git a/e2e/cleanup_test.go b/e2e/cleanup_test.go
index f05360b8..1712ec01 100644
--- a/e2e/cleanup_test.go
+++ b/e2e/cleanup_test.go
@@ -49,7 +49,7 @@ func testCleanup() {
 			for _, node := range nodes.Items {
 				topolvmFinalize := false
 				for _, fn := range node.Finalizers {
-					if fn == topolvm.NodeFinalizer {
+					if fn == topolvm.GetNodeFinalizer() {
 						topolvmFinalize = true
 					}
 				}
@@ -130,7 +130,7 @@ func testCleanup() {
 		}
 		Expect(targetPVC).ShouldNot(BeNil())
 
-		stdout, stderr, err = kubectl("get", "logicalvolume", "-o=json")
+		stdout, stderr, err = kubectl("get", "logicalvolumes", "-o=json")
 		Expect(err).ShouldNot(HaveOccurred(), "stdout=%s, stderr=%s", stdout, stderr)
 		var logicalVolumeList topolvmv1.LogicalVolumeList
 		err = json.Unmarshal(stdout, &logicalVolumeList)
@@ -248,7 +248,7 @@ func testCleanup() {
 		By("confirming logicalvolumes are deleted")
 		Eventually(func() error {
 			for _, lv := range targetLVs {
-				_, _, err := kubectl("get", "logicalvolume", lv.Name)
+				_, _, err := kubectl("get", "logicalvolumes", lv.Name)
 				if err == nil {
 					return fmt.Errorf("logicalvolume still exists: %s", lv.Name)
 				}
@@ -263,7 +263,7 @@ func testCleanup() {
 		}
 		Eventually(func() error {
 			for _, lv := range targetLVs {
-				_, _, err := kubectl("get", "logicalvolume", lv.Name)
+				_, _, err := kubectl("get", "logicalvolumes", lv.Name)
 				if err != nil {
 					return fmt.Errorf("error getting logicalvolume (which should still exist): %s", lv.Name)
 				}
diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go
index e6cfbae7..f42d5e71 100644
--- a/e2e/e2e_test.go
+++ b/e2e/e2e_test.go
@@ -362,7 +362,7 @@ func testE2E() {
 					if node.Name == "topolvm-e2e-control-plane" {
 						continue
 					}
-					strCap, ok := node.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+					strCap, ok := node.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 					if !ok {
 						return fmt.Errorf("capacity is not annotated: %s", node.Name)
 					}
@@ -589,7 +589,7 @@ func testE2E() {
 				continue
 			}
 
-			strCap, ok := node.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+			strCap, ok := node.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 			Expect(ok).To(Equal(true), "capacity is not annotated: "+node.Name)
 			capacity, err := strconv.Atoi(strCap)
 			Expect(err).ShouldNot(HaveOccurred())
@@ -864,7 +864,7 @@ func waitCreatingPodWithPVC(podName, ns string) (string, error) {
 }
 
 func checkLVIsRegisteredInLVM(volName string) error {
-	stdout, stderr, err := kubectl("get", "logicalvolume", "-n", "topolvm-system", volName, "-o=template", "--template={{.metadata.uid}}")
+	stdout, stderr, err := kubectl("get", "logicalvolumes", "-n", "topolvm-system", volName, "-o=template", "--template={{.metadata.uid}}")
 	if err != nil {
 		return fmt.Errorf("err=%v, stdout=%s, stderr=%s", err, stdout, stderr)
 	}
diff --git a/e2e/hook_test.go b/e2e/hook_test.go
index a025c8fe..4b6ead78 100644
--- a/e2e/hook_test.go
+++ b/e2e/hook_test.go
@@ -63,16 +63,16 @@ func testHook() {
 			}
 
 			resources := pod.Spec.Containers[0].Resources
-			_, ok := resources.Limits[topolvm.CapacityResource]
+			_, ok := resources.Limits[topolvm.GetCapacityResource()]
 			if !ok {
 				return errors.New("resources.Limits is not mutated")
 			}
-			_, ok = resources.Requests[topolvm.CapacityResource]
+			_, ok = resources.Requests[topolvm.GetCapacityResource()]
 			if !ok {
 				return errors.New("resources.Requests is not mutated")
 			}
 
-			capacity, ok := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+			capacity, ok := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 			if !ok {
 				return errors.New("not annotated")
 			}
@@ -94,7 +94,7 @@ func testHook() {
 		stdout, stderr, err := kubectlWithInput(hookGenericEphemeralVolumeYAML, "-n", nsHookTest, "apply", "-f", "-")
 		Expect(err).ShouldNot(HaveOccurred(), "stdout=%s, stderr=%s", stdout, stderr)
 
-		By("checking pod is annotated with topolvm.cybozu.com/capacity")
+		By("checking pod is annotated with topolvm.io/capacity")
 		Eventually(func() error {
 			result, stderr, err := kubectl("get", "-n", nsHookTest, "pods/testhttpd", "-o=json")
 			if err != nil {
@@ -108,16 +108,16 @@ func testHook() {
 			}
 
 			resources := pod.Spec.Containers[0].Resources
-			_, ok := resources.Limits[topolvm.CapacityResource]
+			_, ok := resources.Limits[topolvm.GetCapacityResource()]
 			if !ok {
 				return errors.New("resources.Limits is not mutated")
 			}
-			_, ok = resources.Requests[topolvm.CapacityResource]
+			_, ok = resources.Requests[topolvm.GetCapacityResource()]
 			if !ok {
 				return errors.New("resources.Requests is not mutated")
 			}
 
-			capacity, ok := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+			capacity, ok := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 			if !ok {
 				return errors.New("not annotated")
 			}
diff --git a/e2e/lvcreate_options_test.go b/e2e/lvcreate_options_test.go
index dcfffbdf..7842b603 100644
--- a/e2e/lvcreate_options_test.go
+++ b/e2e/lvcreate_options_test.go
@@ -50,7 +50,7 @@ func testLVCreateOptions() {
 			}
 			volumeName := strings.TrimSpace(string(stdout))
 
-			stdout, stderr, err = kubectl("get", "logicalvolume", "-n", "topolvm-system", volumeName, "-o=template", "--template={{.metadata.uid}}")
+			stdout, stderr, err = kubectl("get", "logicalvolumes", "-n", "topolvm-system", volumeName, "-o=template", "--template={{.metadata.uid}}")
 			if err != nil {
 				return fmt.Errorf("failed to get logicalvolume. stdout: %s, stderr: %s, err: %v", stdout, stderr, err)
 			}
diff --git a/e2e/manifests/common/provisioner.yaml b/e2e/manifests/common/provisioner.yaml
index 678aa204..d227f06b 100644
--- a/e2e/manifests/common/provisioner.yaml
+++ b/e2e/manifests/common/provisioner.yaml
@@ -9,44 +9,44 @@ kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner-immediate
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 volumeBindingMode: Immediate
 parameters:
-  "topolvm.cybozu.com/device-class": "ssd"
+  "topolvm.io/device-class": "ssd"
 ---
 kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner2
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 volumeBindingMode: WaitForFirstConsumer
 parameters:
-  "topolvm.cybozu.com/device-class": "hdd1"
+  "topolvm.io/device-class": "hdd1"
 ---
 kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner3
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 volumeBindingMode: WaitForFirstConsumer
 parameters:
-  "topolvm.cybozu.com/device-class": "hdd2"
+  "topolvm.io/device-class": "hdd2"
 ---
 kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner-default
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 volumeBindingMode: WaitForFirstConsumer
 ---
 kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner-mount-option
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 parameters:
   "csi.storage.k8s.io/fstype": "ext4"
-  "topolvm.cybozu.com/device-class": "ssd"
+  "topolvm.io/device-class": "ssd"
 volumeBindingMode: WaitForFirstConsumer
 allowVolumeExpansion: true
 mountOptions:
@@ -56,9 +56,9 @@ kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner-not-found-device
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 parameters:
-  "topolvm.cybozu.com/device-class": "not-found-device"
+  "topolvm.io/device-class": "not-found-device"
 volumeBindingMode: WaitForFirstConsumer
 allowVolumeExpansion: true
 ---
@@ -66,24 +66,24 @@ kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner-raid
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 parameters:
-  "topolvm.cybozu.com/device-class": "raid"
+  "topolvm.io/device-class": "raid"
 ---
 kind: StorageClass
 apiVersion: storage.k8s.io/v1
 metadata:
   name: topolvm-provisioner-thin
-provisioner: topolvm.cybozu.com
+provisioner: topolvm.io
 volumeBindingMode: WaitForFirstConsumer
 parameters:
-  "topolvm.cybozu.com/device-class": "thin"
+  "topolvm.io/device-class": "thin"
 ---
 apiVersion: snapshot.storage.k8s.io/v1
 kind: VolumeSnapshotClass
 metadata:
   name: topolvm-provisioner-thin
-driver: topolvm.cybozu.com
+driver: topolvm.io
 deletionPolicy: Delete
 ---
 apiVersion: v1
diff --git a/e2e/manifests/values/daemonset-lvmd-storage-capacity.yaml b/e2e/manifests/values/daemonset-lvmd-storage-capacity.yaml
index f99d7c70..d8dfe713 100644
--- a/e2e/manifests/values/daemonset-lvmd-storage-capacity.yaml
+++ b/e2e/manifests/values/daemonset-lvmd-storage-capacity.yaml
@@ -13,7 +13,7 @@ controller:
   volumes:
     - name: socket-dir
       hostPath:
-        path: /var/lib/kubelet/plugins/topolvm.cybozu.com/controller
+        path: /var/lib/kubelet/plugins/topolvm.io/controller
         type: DirectoryOrCreate
 
 scheduler:
@@ -55,7 +55,7 @@ storageClasses:
       volumeBindingMode: WaitForFirstConsumer
       allowVolumeExpansion: true
       additionalParameters:
-        topolvm.cybozu.com/device-class: "ssd"
+        topolvm.io/device-class: "ssd"
   - name: topolvm-provisioner-thin
     storageClass:
       fsType: xfs
@@ -63,7 +63,7 @@ storageClasses:
       volumeBindingMode: WaitForFirstConsumer
       allowVolumeExpansion: true
       additionalParameters:
-        topolvm.cybozu.com/device-class: "thin"        
+        topolvm.io/device-class: "thin"
 
 webhook:
   podMutatingWebhook:
diff --git a/e2e/manifests/values/daemonset-scheduler.yaml b/e2e/manifests/values/daemonset-scheduler.yaml
index 2267486f..3659dc82 100644
--- a/e2e/manifests/values/daemonset-scheduler.yaml
+++ b/e2e/manifests/values/daemonset-scheduler.yaml
@@ -13,7 +13,7 @@ controller:
   volumes:
     - name: socket-dir
       hostPath:
-        path: /var/lib/kubelet/plugins/topolvm.cybozu.com/controller
+        path: /var/lib/kubelet/plugins/topolvm.io/controller
         type: DirectoryOrCreate
 
 scheduler:
@@ -33,7 +33,7 @@ storageClasses:
       volumeBindingMode: WaitForFirstConsumer
       allowVolumeExpansion: true
       additionalParameters:
-        topolvm.cybozu.com/device-class: "ssd"
+        topolvm.io/device-class: "ssd"
 
 podSecurityPolicy:
   create: false
diff --git a/e2e/manifests/values/deployment-scheduler.yaml b/e2e/manifests/values/deployment-scheduler.yaml
index b7078563..ef2f4f71 100644
--- a/e2e/manifests/values/deployment-scheduler.yaml
+++ b/e2e/manifests/values/deployment-scheduler.yaml
@@ -13,7 +13,7 @@ controller:
   volumes:
     - name: socket-dir
       hostPath:
-        path: /var/lib/kubelet/plugins/topolvm.cybozu.com/controller
+        path: /var/lib/kubelet/plugins/topolvm.io/controller
         type: DirectoryOrCreate
 
 scheduler:
@@ -43,7 +43,7 @@ storageClasses:
       volumeBindingMode: WaitForFirstConsumer
       allowVolumeExpansion: true
       additionalParameters:
-        topolvm.cybozu.com/device-class: "ssd"
+        topolvm.io/device-class: "ssd"
 
 podSecurityPolicy:
   create: false
diff --git a/e2e/manifests/values/storage-capacity.yaml b/e2e/manifests/values/storage-capacity.yaml
index 5d385685..6e8c8bbe 100644
--- a/e2e/manifests/values/storage-capacity.yaml
+++ b/e2e/manifests/values/storage-capacity.yaml
@@ -15,7 +15,7 @@ controller:
   volumes:
     - name: socket-dir
       hostPath:
-        path: /var/lib/kubelet/plugins/topolvm.cybozu.com/controller
+        path: /var/lib/kubelet/plugins/topolvm.io/controller
         type: DirectoryOrCreate
 
 scheduler:
@@ -35,7 +35,7 @@ storageClasses:
       volumeBindingMode: WaitForFirstConsumer
       allowVolumeExpansion: true
       additionalParameters:
-        topolvm.cybozu.com/device-class: "ssd"
+        topolvm.io/device-class: "ssd"
 
 webhook:
   podMutatingWebhook:
diff --git a/e2e/multiple_vg_test.go b/e2e/multiple_vg_test.go
index 58197583..d623bc1e 100644
--- a/e2e/multiple_vg_test.go
+++ b/e2e/multiple_vg_test.go
@@ -111,7 +111,7 @@ type lvinfo struct {
 }
 
 func getLVInfo(volName string) (*lvinfo, error) {
-	stdout, stderr, err := kubectl("get", "logicalvolume", "-n", "topolvm-system", volName, "-o=template", "--template={{.metadata.uid}}")
+	stdout, stderr, err := kubectl("get", "logicalvolumes", "-n", "topolvm-system", volName, "-o=template", "--template={{.metadata.uid}}")
 	if err != nil {
 		return nil, fmt.Errorf("err=%v, stdout=%s, stderr=%s", err, stdout, stderr)
 	}
diff --git a/e2e/node_test.go b/e2e/node_test.go
index 6c67d47f..8e8aec36 100644
--- a/e2e/node_test.go
+++ b/e2e/node_test.go
@@ -106,7 +106,7 @@ func testNode() {
 				vgName,
 			)
 			Expect(err).ShouldNot(HaveOccurred(), "stdout=%s, stderr=%s", targetBytes, stderr)
-			val, ok := node.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+			val, ok := node.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 			Expect(ok).To(Equal(true), "capacity is not annotated: "+node.Name)
 			Expect(val).To(Equal(strings.TrimSpace(string(targetBytes))), "unexpected capacity: "+node.Name)
 		}
@@ -155,10 +155,10 @@ func testNode() {
 			err = json.Unmarshal(stdout, &node)
 			Expect(err).ShouldNot(HaveOccurred())
 			for k, v := range node.Annotations {
-				if !strings.HasPrefix(k, topolvm.CapacityKeyPrefix) {
+				if !strings.HasPrefix(k, topolvm.GetCapacityKeyPrefix()) {
 					continue
 				}
-				dc := k[len(topolvm.CapacityKeyPrefix):]
+				dc := k[len(topolvm.GetCapacityKeyPrefix()):]
 				if dc == topolvm.DefaultDeviceClassAnnotationName {
 					continue
 				}
diff --git a/e2e/publish_test.go b/e2e/publish_test.go
index af8456ce..989a3a6f 100644
--- a/e2e/publish_test.go
+++ b/e2e/publish_test.go
@@ -67,9 +67,9 @@ func testPublishVolume() {
 		conn *grpc.ClientConn
 	)
 
-	nodeSocket := "/tmp/topolvm/worker1/plugins/topolvm.cybozu.com/node/csi-topolvm.sock"
+	nodeSocket := "/tmp/topolvm/worker1/plugins/topolvm.io/node/csi-topolvm.sock"
 	if isDaemonsetLvmdEnvSet() {
-		nodeSocket = "/var/lib/kubelet/plugins/topolvm.cybozu.com/node/csi-topolvm.sock"
+		nodeSocket = "/var/lib/kubelet/plugins/topolvm.io/node/csi-topolvm.sock"
 	}
 
 	var cc CleanupContext
@@ -114,7 +114,7 @@ func testPublishVolume() {
 
 		var volumeID string
 		Eventually(func() error {
-			stdout, stderr, err := kubectl("get", "logicalvolume", name, "-o", "yaml")
+			stdout, stderr, err := kubectl("get", "logicalvolumes", name, "-o", "yaml")
 			if err != nil {
 				return fmt.Errorf("failed to get logical volume. stdout: %s, stderr: %s, err: %v", stdout, stderr, err)
 			}
@@ -201,7 +201,7 @@ func testPublishVolume() {
 		cl.unregister(volumeID, mountTargetPath)
 
 		By("cleaning logicalvolume")
-		stdout, stderr, err := kubectl("delete", "logicalvolume", "csi-node-test-fs")
+		stdout, stderr, err := kubectl("delete", "logicalvolumes", "csi-node-test-fs")
 		Expect(err).ShouldNot(HaveOccurred(), "stdout=%s, stderr=%s", stdout, stderr)
 	})
 
@@ -221,7 +221,7 @@ func testPublishVolume() {
 
 		var volumeID string
 		Eventually(func() error {
-			stdout, stderr, err := kubectl("get", "logicalvolume", name, "-o", "yaml")
+			stdout, stderr, err := kubectl("get", "logicalvolumes", name, "-o", "yaml")
 			if err != nil {
 				return fmt.Errorf("failed to get logical volume. stdout: %s, stderr: %s, err: %v", stdout, stderr, err)
 			}
@@ -292,7 +292,7 @@ func testPublishVolume() {
 		cl.unregister(volumeID, deviceTargetPath)
 
 		By("cleaning logicalvolume")
-		stdout, stderr, err := kubectl("delete", "logicalvolume", "csi-node-test-block")
+		stdout, stderr, err := kubectl("delete", "logicalvolumes", "csi-node-test-block")
 		Expect(err).ShouldNot(HaveOccurred(), "stdout=%s, stderr=%s", stdout, stderr)
 	})
 
@@ -368,7 +368,7 @@ func testPublishVolume() {
 
 		var volumeID string
 		Eventually(func() error {
-			stdout, stderr, err := kubectl("get", "logicalvolume", name, "-o", "yaml")
+			stdout, stderr, err := kubectl("get", "logicalvolumes", name, "-o", "yaml")
 			if err != nil {
 				return fmt.Errorf("failed to get logical volume. stdout: %s, stderr: %s, err: %v", stdout, stderr, err)
 			}
@@ -412,7 +412,7 @@ func testPublishVolume() {
 		Expect(err).Should(HaveOccurred())
 
 		By("cleaning logicalvolume")
-		stdout, stderr, err := kubectl("delete", "logicalvolume", "csi-node-test-fs")
+		stdout, stderr, err := kubectl("delete", "logicalvolumes", "csi-node-test-fs")
 		Expect(err).ShouldNot(HaveOccurred(), "stdout=%s, stderr=%s", stdout, stderr)
 	})
 }
diff --git a/e2e/run_test.go b/e2e/run_test.go
index 2cb2a231..0120e29e 100644
--- a/e2e/run_test.go
+++ b/e2e/run_test.go
@@ -56,7 +56,7 @@ func commonBeforeEach() CleanupContext {
 	cc.LvmCount, err = countLVMs()
 	ExpectWithOffset(1, err).ShouldNot(HaveOccurred())
 
-	cc.CapacityAnnotations, err = getNodeAnnotationMapWithPrefix(topolvm.CapacityKeyPrefix)
+	cc.CapacityAnnotations, err = getNodeAnnotationMapWithPrefix(topolvm.GetCapacityKeyPrefix())
 	ExpectWithOffset(1, err).ShouldNot(HaveOccurred())
 
 	return cc
@@ -78,7 +78,7 @@ func commonAfterEach(cc CleanupContext) {
 				return fmt.Errorf("stdout=%s, stderr=%s", stdout, stderr)
 			}
 
-			capacitiesAfter, err := getNodeAnnotationMapWithPrefix(topolvm.CapacityKeyPrefix)
+			capacitiesAfter, err := getNodeAnnotationMapWithPrefix(topolvm.GetCapacityKeyPrefix())
 			if err != nil {
 				return err
 			}
diff --git a/e2e/sanity_test.go b/e2e/sanity_test.go
index 7aa83d0a..1370b5f6 100644
--- a/e2e/sanity_test.go
+++ b/e2e/sanity_test.go
@@ -14,9 +14,9 @@ import (
 )
 
 func testSanity() {
-	baseDir := "/tmp/topolvm/worker1/plugins/topolvm.cybozu.com/"
+	baseDir := "/tmp/topolvm/worker1/plugins/topolvm.io/"
 	if isDaemonsetLvmdEnvSet() {
-		baseDir = "/var/lib/kubelet/plugins/topolvm.cybozu.com/"
+		baseDir = "/var/lib/kubelet/plugins/topolvm.io/"
 	}
 
 	It("should add node selector to node DaemonSet for CSI test", func() {
@@ -74,10 +74,10 @@ func testSanity() {
 	}
 
 	if os.Getenv("SANITY_TEST_WITH_THIN_DEVICECLASS") == "true" {
-		// csi.storage.k8s.io/fstype=xfs,topolvm.cybozu.com/device-class=thin
+		// csi.storage.k8s.io/fstype=xfs,topolvm.io/device-class=thin
 		volParams := make(map[string]string)
 		volParams["csi.storage.k8s.io/fstype"] = "xfs"
-		volParams["topolvm.cybozu.com/device-class"] = "thin"
+		volParams["topolvm.io/device-class"] = "thin"
 		tc.TestVolumeParameters = volParams
 	}
 	sanity.GinkgoTest(&tc)
diff --git a/e2e/scheduler-config-daemonset.yaml b/e2e/scheduler-config-daemonset.yaml
index 6372594b..4c97f85b 100644
--- a/e2e/scheduler-config-daemonset.yaml
+++ b/e2e/scheduler-config-daemonset.yaml
@@ -12,5 +12,5 @@ extenders:
   nodeCacheCapable: false
   weight: 1
   managedResources:
-  - name: "topolvm.cybozu.com/capacity"
+  - name: "topolvm.io/capacity"
     ignoredByScheduler: true
diff --git a/e2e/scheduler-config-deployment.yaml b/e2e/scheduler-config-deployment.yaml
index 61388b18..b505e84c 100644
--- a/e2e/scheduler-config-deployment.yaml
+++ b/e2e/scheduler-config-deployment.yaml
@@ -12,5 +12,5 @@ extenders:
   nodeCacheCapable: false
   weight: 1
   managedResources:
-  - name: "topolvm.cybozu.com/capacity"
+  - name: "topolvm.io/capacity"
     ignoredByScheduler: true
diff --git a/e2e/testdata/capacity/pvc-pod-template.yaml b/e2e/testdata/capacity/pvc-pod-template.yaml
index 652a738b..540ea0f3 100644
--- a/e2e/testdata/capacity/pvc-pod-template.yaml
+++ b/e2e/testdata/capacity/pvc-pod-template.yaml
@@ -31,4 +31,4 @@ spec:
       persistentVolumeClaim:
         claimName: %s
   nodeSelector:
-    topology.topolvm.cybozu.com/node: %s
+    topology.topolvm.io/node: %s
diff --git a/e2e/testdata/cloning/clone-pod-template.yaml b/e2e/testdata/cloning/clone-pod-template.yaml
index e853ec0c..536372de 100644
--- a/e2e/testdata/cloning/clone-pod-template.yaml
+++ b/e2e/testdata/cloning/clone-pod-template.yaml
@@ -17,4 +17,4 @@ spec:
       persistentVolumeClaim:
         claimName: %s
   nodeSelector:
-    topology.topolvm.cybozu.com/node: %s
+    topology.topolvm.io/node: %s
diff --git a/e2e/testdata/publish/lv-template.yaml b/e2e/testdata/publish/lv-template.yaml
index de8f1a0e..00b1a125 100644
--- a/e2e/testdata/publish/lv-template.yaml
+++ b/e2e/testdata/publish/lv-template.yaml
@@ -1,4 +1,4 @@
-apiVersion: topolvm.cybozu.com/v1
+apiVersion: topolvm.io/v1
 kind: LogicalVolume
 metadata:
   name: %s
diff --git a/e2e/testdata/scheduler/capacity-pod-template.yaml b/e2e/testdata/scheduler/capacity-pod-template.yaml
index 62f5896d..f1a1179e 100644
--- a/e2e/testdata/scheduler/capacity-pod-template.yaml
+++ b/e2e/testdata/scheduler/capacity-pod-template.yaml
@@ -6,7 +6,7 @@ metadata:
   labels:
     app.kubernetes.io/name: testhttpd
   annotations:
-    capacity.topolvm.cybozu.com/ssd: %q
+    capacity.topolvm.io/ssd: %q
 spec:
   containers:
   - name: ubuntu
@@ -14,6 +14,6 @@ spec:
     command: ["/usr/local/bin/pause"]
     resources:
       requests:
-        topolvm.cybozu.com/capacity: 1
+        topolvm.io/capacity: 1
       limits:
-        topolvm.cybozu.com/capacity: 1
+        topolvm.io/capacity: 1
diff --git a/e2e/testdata/snapshot_restore/restore-pod-template.yaml b/e2e/testdata/snapshot_restore/restore-pod-template.yaml
index e853ec0c..536372de 100644
--- a/e2e/testdata/snapshot_restore/restore-pod-template.yaml
+++ b/e2e/testdata/snapshot_restore/restore-pod-template.yaml
@@ -17,4 +17,4 @@ spec:
       persistentVolumeClaim:
         claimName: %s
   nodeSelector:
-    topology.topolvm.cybozu.com/node: %s
+    topology.topolvm.io/node: %s
diff --git a/e2e/testdata/thin_provisioning/pod-template.yaml b/e2e/testdata/thin_provisioning/pod-template.yaml
index 064ca9f6..e64a7f23 100644
--- a/e2e/testdata/thin_provisioning/pod-template.yaml
+++ b/e2e/testdata/thin_provisioning/pod-template.yaml
@@ -17,4 +17,4 @@ spec:
       persistentVolumeClaim:
         claimName: %s
   nodeSelector:
-    topology.topolvm.cybozu.com/node: %s
+    topology.topolvm.io/node: %s
diff --git a/hook/mutate_pod_test.go b/hook/mutate_pod_test.go
index 7df26cde..d211e0ff 100644
--- a/hook/mutate_pod_test.go
+++ b/hook/mutate_pod_test.go
@@ -158,7 +158,7 @@ var _ = Describe("pod mutation webhook", func() {
 		pod = getPod()
 		Expect(pod.Spec.Containers[0].Resources.Requests).To(BeEmpty())
 		Expect(pod.Spec.Containers[0].Resources.Limits).To(BeEmpty())
-		Expect(pod.Annotations).NotTo(HaveKey(topolvm.CapacityKeyPrefix))
+		Expect(pod.Annotations).NotTo(HaveKey(topolvm.GetCapacityKeyPrefix()))
 	})
 
 	It("should create pod before its PVC", func() {
@@ -189,9 +189,9 @@ var _ = Describe("pod mutation webhook", func() {
 		Expect(err).ShouldNot(HaveOccurred())
 
 		pod = getPod()
-		request := pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.CapacityResource]
-		capacity := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+		request := pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.GetCapacityResource()]
+		capacity := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(limit.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(1 << 30)))
@@ -223,23 +223,23 @@ var _ = Describe("pod mutation webhook", func() {
 		Expect(err).ShouldNot(HaveOccurred())
 
 		pod = getPod()
-		request := pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.CapacityResource]
-		capacity := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+		request := pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.GetCapacityResource()]
+		capacity := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(limit.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(1 << 30)))
 
-		request = pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		limit = pod.Spec.Containers[0].Resources.Limits[topolvm.CapacityResource]
-		capacity = pod.Annotations[topolvm.CapacityKeyPrefix+"hdd1"]
+		request = pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		limit = pod.Spec.Containers[0].Resources.Limits[topolvm.GetCapacityResource()]
+		capacity = pod.Annotations[topolvm.GetCapacityKeyPrefix()+"hdd1"]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(limit.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(3 << 30)))
 
-		request = pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		capacity = pod.Annotations[topolvm.CapacityKeyPrefix+"hdd2"]
-		limit = pod.Spec.Containers[0].Resources.Limits[topolvm.CapacityResource]
+		request = pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		capacity = pod.Annotations[topolvm.GetCapacityKeyPrefix()+"hdd2"]
+		limit = pod.Spec.Containers[0].Resources.Limits[topolvm.GetCapacityResource()]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(limit.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(4 << 30)))
@@ -271,9 +271,9 @@ var _ = Describe("pod mutation webhook", func() {
 		Expect(err).ShouldNot(HaveOccurred())
 
 		pod = getPod()
-		request := pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.CapacityResource]
-		capacity := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+		request := pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.GetCapacityResource()]
+		capacity := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(limit.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(100 << 30)))
@@ -299,9 +299,9 @@ var _ = Describe("pod mutation webhook", func() {
 		Expect(err).ShouldNot(HaveOccurred())
 
 		pod = getPod()
-		request := pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.CapacityResource]
-		capacity := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+		request := pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		limit := pod.Spec.Containers[0].Resources.Limits[topolvm.GetCapacityResource()]
+		capacity := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(limit.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(1 << 30)))
@@ -334,7 +334,7 @@ var _ = Describe("pod mutation webhook", func() {
 		pod = getPod()
 		Expect(pod.Spec.Containers[0].Resources.Requests).To(BeEmpty())
 		Expect(pod.Spec.Containers[0].Resources.Limits).To(BeEmpty())
-		Expect(pod.Annotations).NotTo(HaveKey(topolvm.CapacityKeyPrefix))
+		Expect(pod.Annotations).NotTo(HaveKey(topolvm.GetCapacityKeyPrefix()))
 	})
 
 	It("should calculate requested capacity correctly", func() {
@@ -363,8 +363,8 @@ var _ = Describe("pod mutation webhook", func() {
 		Expect(err).ShouldNot(HaveOccurred())
 
 		pod = getPod()
-		request := pod.Spec.Containers[0].Resources.Requests[topolvm.CapacityResource]
-		capacity := pod.Annotations[topolvm.CapacityKeyPrefix+"ssd"]
+		request := pod.Spec.Containers[0].Resources.Requests[topolvm.GetCapacityResource()]
+		capacity := pod.Annotations[topolvm.GetCapacityKeyPrefix()+"ssd"]
 		Expect(request.Value()).Should(BeNumerically("==", 1))
 		Expect(capacity).Should(Equal(strconv.Itoa(3 << 30)))
 	})
@@ -385,6 +385,6 @@ var _ = Describe("pod mutation webhook", func() {
 		pod = getPod()
 		Expect(pod.Spec.Containers[0].Resources.Requests).To(BeEmpty())
 		Expect(pod.Spec.Containers[0].Resources.Limits).To(BeEmpty())
-		Expect(pod.Annotations).NotTo(HaveKey(topolvm.CapacityKeyPrefix))
+		Expect(pod.Annotations).NotTo(HaveKey(topolvm.GetCapacityKeyPrefix()))
 	})
 })
diff --git a/hook/suite_test.go b/hook/suite_test.go
index 0bee08cc..d04fff8b 100644
--- a/hook/suite_test.go
+++ b/hook/suite_test.go
@@ -57,10 +57,10 @@ func setupCommonResources() {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: topolvmProvisionerStorageClassName,
 		},
-		Provisioner:       "topolvm.cybozu.com",
+		Provisioner:       "topolvm.io",
 		VolumeBindingMode: modePtr(storagev1.VolumeBindingWaitForFirstConsumer),
 		Parameters: map[string]string{
-			topolvm.DeviceClassKey: "ssd",
+			topolvm.GetDeviceClassKey(): "ssd",
 		},
 	}
 	err := k8sClient.Create(testCtx, sc)
@@ -70,10 +70,10 @@ func setupCommonResources() {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: topolvmProvisioner2StorageClassName,
 		},
-		Provisioner:       "topolvm.cybozu.com",
+		Provisioner:       "topolvm.io",
 		VolumeBindingMode: modePtr(storagev1.VolumeBindingWaitForFirstConsumer),
 		Parameters: map[string]string{
-			topolvm.DeviceClassKey: "hdd1",
+			topolvm.GetDeviceClassKey(): "hdd1",
 		},
 	}
 	err = k8sClient.Create(testCtx, sc)
@@ -83,10 +83,10 @@ func setupCommonResources() {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: topolvmProvisioner3StorageClassName,
 		},
-		Provisioner:       "topolvm.cybozu.com",
+		Provisioner:       "topolvm.io",
 		VolumeBindingMode: modePtr(storagev1.VolumeBindingWaitForFirstConsumer),
 		Parameters: map[string]string{
-			topolvm.DeviceClassKey: "hdd2",
+			topolvm.GetDeviceClassKey(): "hdd2",
 		},
 	}
 	err = k8sClient.Create(testCtx, sc)
@@ -96,10 +96,10 @@ func setupCommonResources() {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: topolvmProvisionerImmediateStorageClassName,
 		},
-		Provisioner:       "topolvm.cybozu.com",
+		Provisioner:       "topolvm.io",
 		VolumeBindingMode: modePtr(storagev1.VolumeBindingImmediate),
 		Parameters: map[string]string{
-			topolvm.DeviceClassKey: "ssd",
+			topolvm.GetDeviceClassKey(): "ssd",
 		},
 	}
 	err = k8sClient.Create(testCtx, sc)
@@ -142,7 +142,7 @@ var _ = BeforeSuite(func() {
 				},
 				Webhooks: []admissionv1.MutatingWebhook{
 					{
-						Name:                    "pod-hook.topolvm.cybozu.com",
+						Name:                    "pod-hook.topolvm.io",
 						AdmissionReviewVersions: []string{"v1", "v1beta1"},
 						FailurePolicy:           &failPolicy,
 						ClientConfig: admissionv1.WebhookClientConfig{
diff --git a/scheduler/predicate_test.go b/scheduler/predicate_test.go
index 6a9ee184..cd236aa5 100644
--- a/scheduler/predicate_test.go
+++ b/scheduler/predicate_test.go
@@ -17,9 +17,9 @@ func testNode(name string, cap1Gb, cap2Gb, cap3Gb int64) corev1.Node {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: name,
 			Annotations: map[string]string{
-				topolvm.CapacityKeyPrefix + "ssd":  fmt.Sprintf("%d", cap1Gb<<30),
-				topolvm.CapacityKeyPrefix + "hdd1": fmt.Sprintf("%d", cap2Gb<<30),
-				topolvm.CapacityKeyPrefix + "hdd2": fmt.Sprintf("%d", cap3Gb<<30),
+				topolvm.GetCapacityKeyPrefix() + "ssd":  fmt.Sprintf("%d", cap1Gb<<30),
+				topolvm.GetCapacityKeyPrefix() + "hdd1": fmt.Sprintf("%d", cap2Gb<<30),
+				topolvm.GetCapacityKeyPrefix() + "hdd2": fmt.Sprintf("%d", cap3Gb<<30),
 			},
 		},
 	}
@@ -45,7 +45,7 @@ func TestFilterNodes(t *testing.T) {
 						ObjectMeta: metav1.ObjectMeta{
 							Name: "10.1.1.4",
 							Annotations: map[string]string{
-								topolvm.CapacityKeyPrefix + "ssd": "foo",
+								topolvm.GetCapacityKeyPrefix() + "ssd": "foo",
 							},
 						},
 					},
@@ -139,7 +139,7 @@ func TestExtractRequestedSize(t *testing.T) {
 			input: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
 					Annotations: map[string]string{
-						topolvm.CapacityKeyPrefix + "ssd": strconv.Itoa(5 << 30),
+						topolvm.GetCapacityKeyPrefix() + "ssd": strconv.Itoa(5 << 30),
 					},
 				},
 				Spec: corev1.PodSpec{
@@ -147,20 +147,20 @@ func TestExtractRequestedSize(t *testing.T) {
 						{
 							Resources: corev1.ResourceRequirements{
 								Requests: corev1.ResourceList{
-									topolvm.CapacityResource: *resource.NewQuantity(1, resource.BinarySI),
+									topolvm.GetCapacityResource(): *resource.NewQuantity(1, resource.BinarySI),
 								},
 								Limits: corev1.ResourceList{
-									topolvm.CapacityResource: *resource.NewQuantity(1, resource.BinarySI),
+									topolvm.GetCapacityResource(): *resource.NewQuantity(1, resource.BinarySI),
 								},
 							},
 						},
 						{
 							Resources: corev1.ResourceRequirements{
 								Requests: corev1.ResourceList{
-									topolvm.CapacityResource: *resource.NewQuantity(1, resource.BinarySI),
+									topolvm.GetCapacityResource(): *resource.NewQuantity(1, resource.BinarySI),
 								},
 								Limits: corev1.ResourceList{
-									topolvm.CapacityResource: *resource.NewQuantity(1, resource.BinarySI),
+									topolvm.GetCapacityResource(): *resource.NewQuantity(1, resource.BinarySI),
 								},
 							},
 						},
@@ -175,7 +175,7 @@ func TestExtractRequestedSize(t *testing.T) {
 			input: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
 					Annotations: map[string]string{
-						topolvm.CapacityKeyPrefix + "ssd": strconv.Itoa(3 << 30),
+						topolvm.GetCapacityKeyPrefix() + "ssd": strconv.Itoa(3 << 30),
 					},
 				},
 				Spec: corev1.PodSpec{
@@ -183,7 +183,7 @@ func TestExtractRequestedSize(t *testing.T) {
 						{
 							Resources: corev1.ResourceRequirements{
 								Requests: corev1.ResourceList{
-									topolvm.CapacityResource: *resource.NewQuantity(1, resource.BinarySI),
+									topolvm.GetCapacityResource(): *resource.NewQuantity(1, resource.BinarySI),
 								},
 							},
 						},
diff --git a/scheduler/prioritize_test.go b/scheduler/prioritize_test.go
index 1dc33254..ec0dc998 100644
--- a/scheduler/prioritize_test.go
+++ b/scheduler/prioritize_test.go
@@ -39,9 +39,9 @@ func TestScoreNodes(t *testing.T) {
 	pod := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
 			Annotations: map[string]string{
-				topolvm.CapacityKeyPrefix + "ssd":  "64",
-				topolvm.CapacityKeyPrefix + "hdd1": "64",
-				topolvm.CapacityKeyPrefix + "hdd2": "64",
+				topolvm.GetCapacityKeyPrefix() + "ssd":  "64",
+				topolvm.GetCapacityKeyPrefix() + "hdd1": "64",
+				topolvm.GetCapacityKeyPrefix() + "hdd2": "64",
 			},
 		},
 	}
@@ -56,7 +56,7 @@ func TestScoreNodes(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Name: "10.1.1.3",
 				Annotations: map[string]string{
-					topolvm.CapacityKeyPrefix + "ssd": "foo",
+					topolvm.GetCapacityKeyPrefix() + "ssd": "foo",
 				},
 			},
 		},
diff --git a/scheduler/route_test.go b/scheduler/route_test.go
index b589845d..143bc8ab 100644
--- a/scheduler/route_test.go
+++ b/scheduler/route_test.go
@@ -19,7 +19,7 @@ var extenderArgs = ExtenderArgs{
 	Pod: &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
 			Annotations: map[string]string{
-				topolvm.CapacityKeyPrefix + "ssd": strconv.Itoa(3 << 30),
+				topolvm.GetCapacityKeyPrefix() + "ssd": strconv.Itoa(3 << 30),
 			},
 		},
 		Spec: corev1.PodSpec{
@@ -27,7 +27,7 @@ var extenderArgs = ExtenderArgs{
 				{
 					Resources: corev1.ResourceRequirements{
 						Limits: corev1.ResourceList{
-							topolvm.CapacityResource: *resource.NewQuantity(1, resource.BinarySI),
+							topolvm.GetCapacityResource(): *resource.NewQuantity(1, resource.BinarySI),
 						},
 					},
 				},
diff --git a/util/testing/testing.go b/util/testing/testing.go
new file mode 100644
index 00000000..32c73a56
--- /dev/null
+++ b/util/testing/testing.go
@@ -0,0 +1,12 @@
+package testing
+
+import (
+	"os"
+	"testing"
+)
+
+func DoEnvCheck(t *testing.T) {
+	if os.Getenv("TEST_LEGACY") == "" {
+		t.Skip("Run test using TEST_LEGACY env")
+	}
+}