2018-05-13 13:28:09 +00:00
/ *
2019-07-03 20:20:32 +00:00
Copyright 2019 the Velero contributors .
2018-05-13 13:28:09 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2018-06-08 18:03:07 +00:00
package restic
2018-02-28 01:35:35 +00:00
import (
"context"
"fmt"
"os"
"strings"
"sync"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2019-07-03 20:20:32 +00:00
v1 "k8s.io/api/core/v1"
2018-02-28 01:35:35 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-07-03 20:20:32 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2018-02-28 01:35:35 +00:00
kubeinformers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
2019-01-25 03:33:07 +00:00
"github.com/heptio/velero/pkg/buildinfo"
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/cmd"
"github.com/heptio/velero/pkg/cmd/util/signals"
"github.com/heptio/velero/pkg/controller"
clientset "github.com/heptio/velero/pkg/generated/clientset/versioned"
informers "github.com/heptio/velero/pkg/generated/informers/externalversions"
"github.com/heptio/velero/pkg/restic"
2019-07-03 20:20:32 +00:00
"github.com/heptio/velero/pkg/util/filesystem"
2019-01-25 03:33:07 +00:00
"github.com/heptio/velero/pkg/util/logging"
2018-02-28 01:35:35 +00:00
)
2018-06-08 18:03:07 +00:00
func NewServerCommand ( f client . Factory ) * cobra . Command {
2018-09-25 21:46:29 +00:00
logLevelFlag := logging . LogLevelFlag ( logrus . InfoLevel )
2018-02-28 01:35:35 +00:00
2018-09-25 21:46:29 +00:00
command := & cobra . Command {
2019-06-11 01:20:17 +00:00
Use : "server" ,
Short : "Run the velero restic server" ,
Long : "Run the velero restic server" ,
Hidden : true ,
2018-02-28 01:35:35 +00:00
Run : func ( c * cobra . Command , args [ ] string ) {
logLevel := logLevelFlag . Parse ( )
2018-06-08 18:03:07 +00:00
logrus . Infof ( "Setting log-level to %s" , strings . ToUpper ( logLevel . String ( ) ) )
2018-02-28 01:35:35 +00:00
logger := logging . DefaultLogger ( logLevel )
2019-04-15 17:27:04 +00:00
logger . Infof ( "Starting Velero restic server %s (%s)" , buildinfo . Version , buildinfo . FormattedGitSHA ( ) )
2018-02-28 01:35:35 +00:00
2018-09-25 21:46:29 +00:00
s , err := newResticServer ( logger , fmt . Sprintf ( "%s-%s" , c . Parent ( ) . Name ( ) , c . Name ( ) ) )
2018-02-28 01:35:35 +00:00
cmd . CheckError ( err )
s . run ( )
} ,
}
command . Flags ( ) . Var ( logLevelFlag , "log-level" , fmt . Sprintf ( "the level at which to log. Valid values are %s." , strings . Join ( logLevelFlag . AllowedValues ( ) , ", " ) ) )
return command
}
2018-06-08 18:03:07 +00:00
type resticServer struct {
2019-01-25 03:33:07 +00:00
kubeClient kubernetes . Interface
veleroClient clientset . Interface
veleroInformerFactory informers . SharedInformerFactory
kubeInformerFactory kubeinformers . SharedInformerFactory
podInformer cache . SharedIndexInformer
secretInformer cache . SharedIndexInformer
logger logrus . FieldLogger
ctx context . Context
cancelFunc context . CancelFunc
2019-07-03 20:20:32 +00:00
fileSystem filesystem . Interface
2018-02-28 01:35:35 +00:00
}
2018-09-25 21:46:29 +00:00
func newResticServer ( logger logrus . FieldLogger , baseName string ) ( * resticServer , error ) {
2018-02-28 01:35:35 +00:00
clientConfig , err := client . Config ( "" , "" , baseName )
if err != nil {
return nil , err
}
kubeClient , err := kubernetes . NewForConfig ( clientConfig )
if err != nil {
return nil , errors . WithStack ( err )
}
2019-01-25 03:33:07 +00:00
veleroClient , err := clientset . NewForConfig ( clientConfig )
2018-02-28 01:35:35 +00:00
if err != nil {
return nil , errors . WithStack ( err )
}
// use a stand-alone pod informer because we want to use a field selector to
// filter to only pods scheduled on this node.
podInformer := corev1informers . NewFilteredPodInformer (
kubeClient ,
2018-06-22 19:07:23 +00:00
metav1 . NamespaceAll ,
2018-02-28 01:35:35 +00:00
0 ,
cache . Indexers { cache . NamespaceIndex : cache . MetaNamespaceIndexFunc } ,
func ( opts * metav1 . ListOptions ) {
opts . FieldSelector = fmt . Sprintf ( "spec.nodeName=%s" , os . Getenv ( "NODE_NAME" ) )
} ,
)
2018-06-22 19:07:23 +00:00
// use a stand-alone secrets informer so we can filter to only the restic credentials
2019-01-25 03:33:07 +00:00
// secret(s) within the velero namespace
2018-06-22 19:07:23 +00:00
//
2019-01-25 03:33:07 +00:00
// note: using an informer to access the single secret for all velero-managed
2018-06-22 19:07:23 +00:00
// restic repositories is overkill for now, but will be useful when we move
// to fully-encrypted backups and have unique keys per repository.
secretInformer := corev1informers . NewFilteredSecretInformer (
kubeClient ,
2019-01-25 03:33:07 +00:00
os . Getenv ( "VELERO_NAMESPACE" ) ,
2018-06-22 19:07:23 +00:00
0 ,
cache . Indexers { cache . NamespaceIndex : cache . MetaNamespaceIndexFunc } ,
func ( opts * metav1 . ListOptions ) {
opts . FieldSelector = fmt . Sprintf ( "metadata.name=%s" , restic . CredentialsSecretName )
} ,
)
2018-02-28 01:35:35 +00:00
ctx , cancelFunc := context . WithCancel ( context . Background ( ) )
2019-07-03 20:20:32 +00:00
s := & resticServer {
2019-01-25 03:33:07 +00:00
kubeClient : kubeClient ,
veleroClient : veleroClient ,
veleroInformerFactory : informers . NewFilteredSharedInformerFactory ( veleroClient , 0 , os . Getenv ( "VELERO_NAMESPACE" ) , nil ) ,
kubeInformerFactory : kubeinformers . NewSharedInformerFactory ( kubeClient , 0 ) ,
podInformer : podInformer ,
secretInformer : secretInformer ,
logger : logger ,
ctx : ctx ,
cancelFunc : cancelFunc ,
2019-07-03 20:20:32 +00:00
fileSystem : filesystem . NewFileSystem ( ) ,
}
if err := s . validatePodVolumesHostPath ( ) ; err != nil {
return nil , err
}
return s , nil
2018-02-28 01:35:35 +00:00
}
2018-06-08 18:03:07 +00:00
func ( s * resticServer ) run ( ) {
2018-02-28 01:35:35 +00:00
signals . CancelOnShutdown ( s . cancelFunc , s . logger )
s . logger . Info ( "Starting controllers" )
var wg sync . WaitGroup
backupController := controller . NewPodVolumeBackupController (
s . logger ,
2019-01-25 03:33:07 +00:00
s . veleroInformerFactory . Velero ( ) . V1 ( ) . PodVolumeBackups ( ) ,
s . veleroClient . VeleroV1 ( ) ,
2018-02-28 01:35:35 +00:00
s . podInformer ,
2018-06-22 19:07:23 +00:00
s . secretInformer ,
2018-02-28 01:35:35 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumeClaims ( ) ,
2019-07-10 22:16:21 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumes ( ) ,
2019-01-25 03:33:07 +00:00
s . veleroInformerFactory . Velero ( ) . V1 ( ) . BackupStorageLocations ( ) ,
2018-02-28 01:35:35 +00:00
os . Getenv ( "NODE_NAME" ) ,
)
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
backupController . Run ( s . ctx , 1 )
} ( )
restoreController := controller . NewPodVolumeRestoreController (
s . logger ,
2019-01-25 03:33:07 +00:00
s . veleroInformerFactory . Velero ( ) . V1 ( ) . PodVolumeRestores ( ) ,
s . veleroClient . VeleroV1 ( ) ,
2018-02-28 01:35:35 +00:00
s . podInformer ,
2018-06-22 19:07:23 +00:00
s . secretInformer ,
2018-02-28 01:35:35 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumeClaims ( ) ,
2019-07-10 22:16:21 +00:00
s . kubeInformerFactory . Core ( ) . V1 ( ) . PersistentVolumes ( ) ,
2019-01-25 03:33:07 +00:00
s . veleroInformerFactory . Velero ( ) . V1 ( ) . BackupStorageLocations ( ) ,
2018-02-28 01:35:35 +00:00
os . Getenv ( "NODE_NAME" ) ,
)
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
restoreController . Run ( s . ctx , 1 )
} ( )
2019-01-25 03:33:07 +00:00
go s . veleroInformerFactory . Start ( s . ctx . Done ( ) )
2018-02-28 01:35:35 +00:00
go s . kubeInformerFactory . Start ( s . ctx . Done ( ) )
go s . podInformer . Run ( s . ctx . Done ( ) )
2018-06-22 19:07:23 +00:00
go s . secretInformer . Run ( s . ctx . Done ( ) )
2018-02-28 01:35:35 +00:00
s . logger . Info ( "Controllers started successfully" )
<- s . ctx . Done ( )
s . logger . Info ( "Waiting for all controllers to shut down gracefully" )
wg . Wait ( )
}
2019-07-03 20:20:32 +00:00
// validatePodVolumesHostPath validates that the pod volumes path contains a
// directory for each Pod running on this node
func ( s * resticServer ) validatePodVolumesHostPath ( ) error {
files , err := s . fileSystem . ReadDir ( "/host_pods/" )
if err != nil {
return errors . Wrap ( err , "could not read pod volumes host path" )
}
// create a map of directory names inside the pod volumes path
dirs := sets . NewString ( )
for _ , f := range files {
if f . IsDir ( ) {
dirs . Insert ( f . Name ( ) )
}
}
pods , err := s . kubeClient . CoreV1 ( ) . Pods ( "" ) . List ( metav1 . ListOptions { FieldSelector : fmt . Sprintf ( "spec.nodeName=%s,status.phase=Running" , os . Getenv ( "NODE_NAME" ) ) } )
if err != nil {
return errors . WithStack ( err )
}
valid := true
for _ , pod := range pods . Items {
dirName := string ( pod . GetUID ( ) )
// if the pod is a mirror pod, the directory name is the hash value of the
// mirror pod annotation
if hash , ok := pod . GetAnnotations ( ) [ v1 . MirrorPodAnnotationKey ] ; ok {
dirName = hash
}
if ! dirs . Has ( dirName ) {
valid = false
s . logger . WithFields ( logrus . Fields {
"pod" : fmt . Sprintf ( "%s/%s" , pod . GetNamespace ( ) , pod . GetName ( ) ) ,
"path" : "/host_pods/" + dirName ,
} ) . Debug ( "could not find volumes for pod in host path" )
}
}
if ! valid {
return errors . New ( "unexpected directory structure for host-pods volume, ensure that the host-pods volume corresponds to the pods subdirectory of the kubelet root directory" )
}
return nil
}