1/*
2Copyright 2019 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package plugins
18
19import (
20	"fmt"
21	"strings"
22
23	v1 "k8s.io/api/core/v1"
24	storage "k8s.io/api/storage/v1"
25	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26)
27
28const (
29	// CinderDriverName is the name of the CSI driver for Cinder
30	CinderDriverName = "cinder.csi.openstack.org"
31	// CinderTopologyKey is the zonal topology key for Cinder CSI Driver
32	CinderTopologyKey = "topology.cinder.csi.openstack.org/zone"
33	// CinderInTreePluginName is the name of the intree plugin for Cinder
34	CinderInTreePluginName = "kubernetes.io/cinder"
35)
36
37var _ InTreePlugin = (*osCinderCSITranslator)(nil)
38
39// osCinderCSITranslator handles translation of PV spec from In-tree Cinder to CSI Cinder and vice versa
40type osCinderCSITranslator struct{}
41
42// NewOpenStackCinderCSITranslator returns a new instance of osCinderCSITranslator
43func NewOpenStackCinderCSITranslator() InTreePlugin {
44	return &osCinderCSITranslator{}
45}
46
47// TranslateInTreeStorageClassParametersToCSI translates InTree Cinder storage class parameters to CSI storage class
48func (t *osCinderCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
49	var (
50		params = map[string]string{}
51	)
52	for k, v := range sc.Parameters {
53		switch strings.ToLower(k) {
54		case fsTypeKey:
55			params[csiFsTypeKey] = v
56		default:
57			// All other parameters are supported by the CSI driver.
58			// This includes also "availability", therefore do not translate it to sc.AllowedTopologies
59			params[k] = v
60		}
61	}
62
63	if len(sc.AllowedTopologies) > 0 {
64		newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, CinderTopologyKey)
65		if err != nil {
66			return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
67		}
68		sc.AllowedTopologies = newTopologies
69	}
70
71	sc.Parameters = params
72
73	return sc, nil
74}
75
76// TranslateInTreeInlineVolumeToCSI takes a Volume with Cinder set from in-tree
77// and converts the Cinder source to a CSIPersistentVolumeSource
78func (t *osCinderCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
79	if volume == nil || volume.Cinder == nil {
80		return nil, fmt.Errorf("volume is nil or Cinder not defined on volume")
81	}
82
83	cinderSource := volume.Cinder
84	pv := &v1.PersistentVolume{
85		ObjectMeta: metav1.ObjectMeta{
86			// Must be unique per disk as it is used as the unique part of the
87			// staging path
88			Name: fmt.Sprintf("%s-%s", CinderDriverName, cinderSource.VolumeID),
89		},
90		Spec: v1.PersistentVolumeSpec{
91			PersistentVolumeSource: v1.PersistentVolumeSource{
92				CSI: &v1.CSIPersistentVolumeSource{
93					Driver:           CinderDriverName,
94					VolumeHandle:     cinderSource.VolumeID,
95					ReadOnly:         cinderSource.ReadOnly,
96					FSType:           cinderSource.FSType,
97					VolumeAttributes: map[string]string{},
98				},
99			},
100			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
101		},
102	}
103	return pv, nil
104}
105
106// TranslateInTreePVToCSI takes a PV with Cinder set from in-tree
107// and converts the Cinder source to a CSIPersistentVolumeSource
108func (t *osCinderCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
109	if pv == nil || pv.Spec.Cinder == nil {
110		return nil, fmt.Errorf("pv is nil or Cinder not defined on pv")
111	}
112
113	cinderSource := pv.Spec.Cinder
114
115	csiSource := &v1.CSIPersistentVolumeSource{
116		Driver:           CinderDriverName,
117		VolumeHandle:     cinderSource.VolumeID,
118		ReadOnly:         cinderSource.ReadOnly,
119		FSType:           cinderSource.FSType,
120		VolumeAttributes: map[string]string{},
121	}
122
123	if err := translateTopologyFromInTreeToCSI(pv, CinderTopologyKey); err != nil {
124		return nil, fmt.Errorf("failed to translate topology: %v", err)
125	}
126
127	pv.Spec.Cinder = nil
128	pv.Spec.CSI = csiSource
129	return pv, nil
130}
131
132// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
133// translates the Cinder CSI source to a Cinder In-tree source.
134func (t *osCinderCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
135	if pv == nil || pv.Spec.CSI == nil {
136		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
137	}
138
139	csiSource := pv.Spec.CSI
140
141	cinderSource := &v1.CinderPersistentVolumeSource{
142		VolumeID: csiSource.VolumeHandle,
143		FSType:   csiSource.FSType,
144		ReadOnly: csiSource.ReadOnly,
145	}
146
147	// translate CSI topology to In-tree topology for rollback compatibility.
148	// It is not possible to guess Cinder Region from the Zone, therefore leave it empty.
149	if err := translateTopologyFromCSIToInTree(pv, CinderTopologyKey, nil); err != nil {
150		return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err)
151	}
152
153	pv.Spec.CSI = nil
154	pv.Spec.Cinder = cinderSource
155	return pv, nil
156}
157
158// CanSupport tests whether the plugin supports a given persistent volume
159// specification from the API.  The spec pointer should be considered
160// const.
161func (t *osCinderCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
162	return pv != nil && pv.Spec.Cinder != nil
163}
164
165// CanSupportInline tests whether the plugin supports a given inline volume
166// specification from the API.  The spec pointer should be considered
167// const.
168func (t *osCinderCSITranslator) CanSupportInline(volume *v1.Volume) bool {
169	return volume != nil && volume.Cinder != nil
170}
171
172// GetInTreePluginName returns the name of the intree plugin driver
173func (t *osCinderCSITranslator) GetInTreePluginName() string {
174	return CinderInTreePluginName
175}
176
177// GetCSIPluginName returns the name of the CSI plugin
178func (t *osCinderCSITranslator) GetCSIPluginName() string {
179	return CinderDriverName
180}
181
182func (t *osCinderCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
183	return volumeHandle, nil
184}
185