Skip to content
Snippets Groups Projects
Commit 58fe87e7 authored by Sheng Yang's avatar Sheng Yang
Browse files

Add NodePathMap for configuration

parent 451ce50e
No related branches found
No related tags found
No related merge requests found
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: default
data:
config.json: |-
{
"nodePathMap":[
{
"node":"yasker-lp-dev1",
"paths":["/opt", "/data1"]
},
{
"node":"yasker-lp-dev2",
"paths":["/opt"]
},
{
"node":"yasker-lp-dev3",
"paths":["/data3"]
}
]
}
...@@ -52,6 +52,7 @@ spec: ...@@ -52,6 +52,7 @@ spec:
labels: labels:
app: local-path-provisioner app: local-path-provisioner
spec: spec:
serviceAccountName: local-path-provisioner-service-account
containers: containers:
- name: local-path-provisioner - name: local-path-provisioner
image: yasker/local-path-provisioner:63df7c1 image: yasker/local-path-provisioner:63df7c1
...@@ -60,4 +61,12 @@ spec: ...@@ -60,4 +61,12 @@ spec:
- local-path-provisioner - local-path-provisioner
- --debug - --debug
- start - start
serviceAccountName: local-path-provisioner-service-account - --config
- /etc/config/config.json
volumeMounts:
- name: config-volume
mountPath: /etc/config/
volumes:
- name: config-volume
configMap:
name: local-path-config
...@@ -22,6 +22,7 @@ var ( ...@@ -22,6 +22,7 @@ var (
FlagProvisionerName = "provisioner-name" FlagProvisionerName = "provisioner-name"
EnvProvisionerName = "PROVISIONER_NAME" EnvProvisionerName = "PROVISIONER_NAME"
FlagConfigFile = "config"
) )
func cmdNotFound(c *cli.Context, command string) { func cmdNotFound(c *cli.Context, command string) {
...@@ -48,10 +49,15 @@ func StartCmd() cli.Command { ...@@ -48,10 +49,15 @@ func StartCmd() cli.Command {
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: FlagProvisionerName, Name: FlagProvisionerName,
Usage: "Specify provisioner name", Usage: "Optional. Specify provisioner name.",
EnvVar: EnvProvisionerName, EnvVar: EnvProvisionerName,
Value: DefaultProvisionerName, Value: DefaultProvisionerName,
}, },
cli.StringFlag{
Name: FlagConfigFile,
Usage: "Required. Provisioner configuration file.",
Value: "",
},
}, },
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
if err := startDaemon(c); err != nil { if err := startDaemon(c); err != nil {
...@@ -84,7 +90,11 @@ func startDaemon(c *cli.Context) error { ...@@ -84,7 +90,11 @@ func startDaemon(c *cli.Context) error {
if provisionerName == "" { if provisionerName == "" {
return fmt.Errorf("invalid empty provisioner name") return fmt.Errorf("invalid empty provisioner name")
} }
provisioner := NewProvisioner(kubeClient) configFile := c.String(FlagConfigFile)
provisioner, err := NewProvisioner(kubeClient, configFile)
if err != nil {
return err
}
pc := pvController.NewProvisionController( pc := pvController.NewProvisionController(
kubeClient, kubeClient,
provisionerName, provisionerName,
......
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"time" "time"
...@@ -19,18 +21,55 @@ const ( ...@@ -19,18 +21,55 @@ const (
) )
var ( var (
DefaultPath = "/opt" CleanupTimeoutCounts = 120
CleanupCounts = 120
) )
type LocalPathProvisioner struct { type LocalPathProvisioner struct {
kubeClient *clientset.Clientset kubeClient *clientset.Clientset
configFile string
} }
func NewProvisioner(kubeClient *clientset.Clientset) *LocalPathProvisioner { type NodePathMapData struct {
Node string `json:"node,omitempty"`
Paths []string `json:"paths,omitempty"`
}
type ConfigData struct {
NodePathMap []*NodePathMapData `json:"nodePathMap,omitempty"`
}
type NodePathMap struct {
Paths map[string]struct{}
}
type Config struct {
NodePathMap map[string]*NodePathMap
}
func (c *Config) getRandomPathOnNode(node string) (string, error) {
npMap := c.NodePathMap[node]
if npMap == nil {
return "", fmt.Errorf("config doesn't contain node %v", node)
}
paths := npMap.Paths
if len(paths) == 0 {
return "", fmt.Errorf("no local path available on node %v", node)
}
path := ""
for path = range paths {
break
}
return path, nil
}
func NewProvisioner(kubeClient *clientset.Clientset, configFile string) (*LocalPathProvisioner, error) {
if _, err := getConfig(configFile); err != nil {
return nil, errors.Wrapf(err, "invalidate config file %v", configFile)
}
return &LocalPathProvisioner{ return &LocalPathProvisioner{
kubeClient: kubeClient, kubeClient: kubeClient,
} configFile: configFile,
}, nil
} }
func (p *LocalPathProvisioner) Provision(opts pvController.VolumeOptions) (*v1.PersistentVolume, error) { func (p *LocalPathProvisioner) Provision(opts pvController.VolumeOptions) (*v1.PersistentVolume, error) {
...@@ -47,10 +86,20 @@ func (p *LocalPathProvisioner) Provision(opts pvController.VolumeOptions) (*v1.P ...@@ -47,10 +86,20 @@ func (p *LocalPathProvisioner) Provision(opts pvController.VolumeOptions) (*v1.P
if opts.SelectedNode == nil { if opts.SelectedNode == nil {
return nil, fmt.Errorf("configuration error, no node was specified") return nil, fmt.Errorf("configuration error, no node was specified")
} }
cfg, err := getConfig(p.configFile)
if err != nil {
return nil, err
}
basePath, err := cfg.getRandomPathOnNode(node.Name)
if err != nil {
return nil, err
}
name := opts.PVName name := opts.PVName
path := filepath.Join(DefaultPath, name) path := filepath.Join(basePath, name)
logrus.Infof("Created volume %v", name) logrus.Infof("Created volume %v at %v:%v", name, node.Name, path)
fs := v1.PersistentVolumeFilesystem fs := v1.PersistentVolumeFilesystem
hostPathType := v1.HostPathDirectoryOrCreate hostPathType := v1.HostPathDirectoryOrCreate
...@@ -159,9 +208,12 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error ...@@ -159,9 +208,12 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error
if name == "" || path == "" || node == "" { if name == "" || path == "" || node == "" {
return fmt.Errorf("invalid empty name or path or node") return fmt.Errorf("invalid empty name or path or node")
} }
//TODO match up with node prefixes, make sure it's one of them (and not `/`) path, err = filepath.Abs(path)
if filepath.Clean(path) == "/" { if err != nil {
return fmt.Errorf("not sure why you want to DESTROY the root directory") return err
}
if path == "/" {
return fmt.Errorf("not sure why you want to DESTROY THE ROOT DIRECTORY")
} }
hostPathType := v1.HostPathDirectoryOrCreate hostPathType := v1.HostPathDirectoryOrCreate
cleanupPod := &v1.Pod{ cleanupPod := &v1.Pod{
...@@ -170,6 +222,7 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error ...@@ -170,6 +222,7 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error
}, },
Spec: v1.PodSpec{ Spec: v1.PodSpec{
RestartPolicy: v1.RestartPolicyNever, RestartPolicy: v1.RestartPolicyNever,
NodeName: node,
Containers: []v1.Container{ Containers: []v1.Container{
{ {
Name: "local-path-cleanup", Name: "local-path-cleanup",
...@@ -213,7 +266,7 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error ...@@ -213,7 +266,7 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error
}() }()
completed := false completed := false
for i := 0; i < CleanupCounts; i++ { for i := 0; i < CleanupTimeoutCounts; i++ {
if pod, err := p.kubeClient.CoreV1().Pods(namespace).Get(pod.Name, metav1.GetOptions{}); err != nil { if pod, err := p.kubeClient.CoreV1().Pods(namespace).Get(pod.Name, metav1.GetOptions{}); err != nil {
return err return err
} else if pod.Status.Phase == v1.PodSucceeded { } else if pod.Status.Phase == v1.PodSucceeded {
...@@ -223,9 +276,59 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error ...@@ -223,9 +276,59 @@ func (p *LocalPathProvisioner) cleanupVolume(name, path, node string) (err error
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
if !completed { if !completed {
return fmt.Errorf("cleanup process timeout after %v seconds", CleanupCounts) return fmt.Errorf("cleanup process timeout after %v seconds", CleanupTimeoutCounts)
} }
logrus.Infof("Volume %v has been cleaned up", name) logrus.Infof("Volume %v has been cleaned up from %v:%v", name, node, path)
return nil return nil
} }
func getConfig(configFile string) (cfg *Config, err error) {
defer func() {
err = errors.Wrapf(err, "fail to load config file %v", configFile)
}()
f, err := os.Open(configFile)
if err != nil {
return nil, err
}
defer f.Close()
var data ConfigData
if err := json.NewDecoder(f).Decode(&data); err != nil {
return nil, err
}
cfg, err = canonicalizeConfig(&data)
if err != nil {
return nil, err
}
return cfg, nil
}
func canonicalizeConfig(data *ConfigData) (cfg *Config, err error) {
defer func() {
err = errors.Wrapf(err, "config canonicalization failed")
}()
cfg = &Config{}
cfg.NodePathMap = map[string]*NodePathMap{}
for _, n := range data.NodePathMap {
if cfg.NodePathMap[n.Node] != nil {
return nil, fmt.Errorf("duplicate node %v", n.Node)
}
npMap := &NodePathMap{Paths: map[string]struct{}{}}
cfg.NodePathMap[n.Node] = npMap
for _, p := range n.Paths {
if p[0] != '/' {
return nil, fmt.Errorf("path must start with / for path %v on node %v", p, n.Node)
}
path, err := filepath.Abs(p)
if err != nil {
return nil, err
}
if _, ok := npMap.Paths[path]; ok {
return nil, fmt.Errorf("duplicate path %v on node %v", p, n.Node)
}
npMap.Paths[path] = struct{}{}
}
}
return cfg, nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment