exit server if not all Ark CRDs exist at startup
Signed-off-by: Steve Kriss <steve@heptio.com>pull/683/head
parent
e11634bfbc
commit
1df9a8a38d
|
@ -41,27 +41,41 @@ func Resource(resource string) schema.GroupResource {
|
||||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type typeInfo struct {
|
||||||
|
PluralName string
|
||||||
|
ItemType runtime.Object
|
||||||
|
ItemListType runtime.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTypeInfo(pluralName string, itemType, itemListType runtime.Object) typeInfo {
|
||||||
|
return typeInfo{
|
||||||
|
PluralName: pluralName,
|
||||||
|
ItemType: itemType,
|
||||||
|
ItemListType: itemListType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomResources returns a map of all custom resources within the Ark
|
||||||
|
// API group, keyed on Kind.
|
||||||
|
func CustomResources() map[string]typeInfo {
|
||||||
|
return map[string]typeInfo{
|
||||||
|
"Backup": newTypeInfo("backups", &Backup{}, &BackupList{}),
|
||||||
|
"Restore": newTypeInfo("restores", &Restore{}, &RestoreList{}),
|
||||||
|
"Schedule": newTypeInfo("schedules", &Schedule{}, &ScheduleList{}),
|
||||||
|
"Config": newTypeInfo("configs", &Config{}, &ConfigList{}),
|
||||||
|
"DownloadRequest": newTypeInfo("downloadrequests", &DownloadRequest{}, &DownloadRequestList{}),
|
||||||
|
"DeleteBackupRequest": newTypeInfo("deletebackuprequests", &DeleteBackupRequest{}, &DeleteBackupRequestList{}),
|
||||||
|
"PodVolumeBackup": newTypeInfo("podvolumebackups", &PodVolumeBackup{}, &PodVolumeBackupList{}),
|
||||||
|
"PodVolumeRestore": newTypeInfo("podvolumerestores", &PodVolumeRestore{}, &PodVolumeRestoreList{}),
|
||||||
|
"ResticRepository": newTypeInfo("resticrepositories", &ResticRepository{}, &ResticRepositoryList{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
for _, typeInfo := range CustomResources() {
|
||||||
&Backup{},
|
scheme.AddKnownTypes(SchemeGroupVersion, typeInfo.ItemType, typeInfo.ItemListType)
|
||||||
&BackupList{},
|
}
|
||||||
&Schedule{},
|
|
||||||
&ScheduleList{},
|
|
||||||
&Restore{},
|
|
||||||
&RestoreList{},
|
|
||||||
&Config{},
|
|
||||||
&ConfigList{},
|
|
||||||
&DownloadRequest{},
|
|
||||||
&DownloadRequestList{},
|
|
||||||
&DeleteBackupRequest{},
|
|
||||||
&DeleteBackupRequestList{},
|
|
||||||
&PodVolumeBackup{},
|
|
||||||
&PodVolumeBackupList{},
|
|
||||||
&PodVolumeRestore{},
|
|
||||||
&PodVolumeRestoreList{},
|
|
||||||
&ResticRepository{},
|
|
||||||
&ResticRepositoryList{},
|
|
||||||
)
|
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
|
@ -150,6 +152,7 @@ type server struct {
|
||||||
backupService cloudprovider.BackupService
|
backupService cloudprovider.BackupService
|
||||||
snapshotService cloudprovider.SnapshotService
|
snapshotService cloudprovider.SnapshotService
|
||||||
discoveryClient discovery.DiscoveryInterface
|
discoveryClient discovery.DiscoveryInterface
|
||||||
|
discoveryHelper arkdiscovery.Helper
|
||||||
dynamicClient dynamic.Interface
|
dynamicClient dynamic.Interface
|
||||||
sharedInformerFactory informers.SharedInformerFactory
|
sharedInformerFactory informers.SharedInformerFactory
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
@ -218,6 +221,15 @@ func (s *server) run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.initDiscoveryHelper(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to ensure all Ark CRDs exist
|
||||||
|
if err := s.arkResourcesExist(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
originalConfig, err := s.loadConfig()
|
originalConfig, err := s.loadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -264,6 +276,68 @@ func (s *server) namespaceExists(namespace string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initDiscoveryHelper instantiates the server's discovery helper and spawns a
|
||||||
|
// goroutine to call Refresh() every 5 minutes.
|
||||||
|
func (s *server) initDiscoveryHelper() error {
|
||||||
|
discoveryHelper, err := arkdiscovery.NewHelper(s.discoveryClient, s.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.discoveryHelper = discoveryHelper
|
||||||
|
|
||||||
|
go wait.Until(
|
||||||
|
func() {
|
||||||
|
if err := discoveryHelper.Refresh(); err != nil {
|
||||||
|
s.logger.WithError(err).Error("Error refreshing discovery")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
5*time.Minute,
|
||||||
|
s.ctx.Done(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// arkResourcesExist checks for the existence of each Ark CRD via discovery
|
||||||
|
// and returns an error if any of them don't exist.
|
||||||
|
func (s *server) arkResourcesExist() error {
|
||||||
|
s.logger.Info("Checking existence of Ark custom resource definitions")
|
||||||
|
|
||||||
|
var arkGroupVersion *metav1.APIResourceList
|
||||||
|
for _, gv := range s.discoveryHelper.Resources() {
|
||||||
|
if gv.GroupVersion == api.SchemeGroupVersion.String() {
|
||||||
|
arkGroupVersion = gv
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arkGroupVersion == nil {
|
||||||
|
return errors.Errorf("Ark API group %s not found", api.SchemeGroupVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundResources := sets.NewString()
|
||||||
|
for _, resource := range arkGroupVersion.APIResources {
|
||||||
|
foundResources.Insert(resource.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for kind := range api.CustomResources() {
|
||||||
|
if foundResources.Has(kind) {
|
||||||
|
s.logger.WithField("kind", kind).Debug("Found custom resource")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
errs = append(errs, errors.Errorf("custom resource %s not found in Ark API group %s", kind, api.SchemeGroupVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return kubeerrs.NewAggregate(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("All Ark custom resource definitions exist")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) loadConfig() (*api.Config, error) {
|
func (s *server) loadConfig() (*api.Config, error) {
|
||||||
s.logger.Info("Retrieving Ark configuration")
|
s.logger.Info("Retrieving Ark configuration")
|
||||||
var (
|
var (
|
||||||
|
@ -542,27 +616,13 @@ func (s *server) runControllers(config *api.Config) error {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
discoveryHelper, err := arkdiscovery.NewHelper(s.discoveryClient, s.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go wait.Until(
|
|
||||||
func() {
|
|
||||||
if err := discoveryHelper.Refresh(); err != nil {
|
|
||||||
s.logger.WithError(err).Error("Error refreshing discovery")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
5*time.Minute,
|
|
||||||
ctx.Done(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.RestoreOnlyMode {
|
if config.RestoreOnlyMode {
|
||||||
s.logger.Info("Restore only mode - not starting the backup, schedule, delete-backup, or GC controllers")
|
s.logger.Info("Restore only mode - not starting the backup, schedule, delete-backup, or GC controllers")
|
||||||
} else {
|
} else {
|
||||||
backupTracker := controller.NewBackupTracker()
|
backupTracker := controller.NewBackupTracker()
|
||||||
|
|
||||||
backupper, err := backup.NewKubernetesBackupper(
|
backupper, err := backup.NewKubernetesBackupper(
|
||||||
discoveryHelper,
|
s.discoveryHelper,
|
||||||
client.NewDynamicFactory(s.dynamicClient),
|
client.NewDynamicFactory(s.dynamicClient),
|
||||||
podexec.NewPodCommandExecutor(s.kubeClientConfig, s.kubeClient.CoreV1().RESTClient()),
|
podexec.NewPodCommandExecutor(s.kubeClientConfig, s.kubeClient.CoreV1().RESTClient()),
|
||||||
s.snapshotService,
|
s.snapshotService,
|
||||||
|
@ -638,7 +698,7 @@ func (s *server) runControllers(config *api.Config) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
restorer, err := restore.NewKubernetesRestorer(
|
restorer, err := restore.NewKubernetesRestorer(
|
||||||
discoveryHelper,
|
s.discoveryHelper,
|
||||||
client.NewDynamicFactory(s.dynamicClient),
|
client.NewDynamicFactory(s.dynamicClient),
|
||||||
s.backupService,
|
s.backupService,
|
||||||
s.snapshotService,
|
s.snapshotService,
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||||
arktest "github.com/heptio/ark/pkg/util/test"
|
arktest "github.com/heptio/ark/pkg/util/test"
|
||||||
)
|
)
|
||||||
|
@ -51,3 +53,47 @@ func TestApplyConfigDefaults(t *testing.T) {
|
||||||
assert.Equal(t, 3*time.Minute, c.ScheduleSyncPeriod.Duration)
|
assert.Equal(t, 3*time.Minute, c.ScheduleSyncPeriod.Duration)
|
||||||
assert.Equal(t, []string{"a", "b"}, c.ResourcePriorities)
|
assert.Equal(t, []string{"a", "b"}, c.ResourcePriorities)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArkResourcesExist(t *testing.T) {
|
||||||
|
var (
|
||||||
|
fakeDiscoveryHelper = &arktest.FakeDiscoveryHelper{}
|
||||||
|
server = &server{
|
||||||
|
logger: arktest.NewLogger(),
|
||||||
|
discoveryHelper: fakeDiscoveryHelper,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ark API group doesn't exist in discovery: should error
|
||||||
|
fakeDiscoveryHelper.ResourceList = []*metav1.APIResourceList{
|
||||||
|
{
|
||||||
|
GroupVersion: "foo/v1",
|
||||||
|
APIResources: []metav1.APIResource{
|
||||||
|
{
|
||||||
|
Name: "Backups",
|
||||||
|
Kind: "Backup",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Error(t, server.arkResourcesExist())
|
||||||
|
|
||||||
|
// Ark API group doesn't contain any custom resources: should error
|
||||||
|
arkAPIResourceList := &metav1.APIResourceList{
|
||||||
|
GroupVersion: v1.SchemeGroupVersion.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeDiscoveryHelper.ResourceList = append(fakeDiscoveryHelper.ResourceList, arkAPIResourceList)
|
||||||
|
assert.Error(t, server.arkResourcesExist())
|
||||||
|
|
||||||
|
// Ark API group contains all custom resources: should not error
|
||||||
|
for kind := range v1.CustomResources() {
|
||||||
|
arkAPIResourceList.APIResources = append(arkAPIResourceList.APIResources, metav1.APIResource{
|
||||||
|
Kind: kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.NoError(t, server.arkResourcesExist())
|
||||||
|
|
||||||
|
// Ark API group contains some but not all custom resources: should error
|
||||||
|
arkAPIResourceList.APIResources = arkAPIResourceList.APIResources[:3]
|
||||||
|
assert.Error(t, server.arkResourcesExist())
|
||||||
|
}
|
||||||
|
|
|
@ -27,17 +27,13 @@ import (
|
||||||
|
|
||||||
// CRDs returns a list of the CRD types for all of the required Ark CRDs
|
// CRDs returns a list of the CRD types for all of the required Ark CRDs
|
||||||
func CRDs() []*apiextv1beta1.CustomResourceDefinition {
|
func CRDs() []*apiextv1beta1.CustomResourceDefinition {
|
||||||
return []*apiextv1beta1.CustomResourceDefinition{
|
var crds []*apiextv1beta1.CustomResourceDefinition
|
||||||
crd("Backup", "backups"),
|
|
||||||
crd("Schedule", "schedules"),
|
for kind, typeInfo := range arkv1.CustomResources() {
|
||||||
crd("Restore", "restores"),
|
crds = append(crds, crd(kind, typeInfo.PluralName))
|
||||||
crd("Config", "configs"),
|
|
||||||
crd("DownloadRequest", "downloadrequests"),
|
|
||||||
crd("DeleteBackupRequest", "deletebackuprequests"),
|
|
||||||
crd("PodVolumeBackup", "podvolumebackups"),
|
|
||||||
crd("PodVolumeRestore", "podvolumerestores"),
|
|
||||||
crd("ResticRepository", "resticrepositories"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return crds
|
||||||
}
|
}
|
||||||
|
|
||||||
func crd(kind, plural string) *apiextv1beta1.CustomResourceDefinition {
|
func crd(kind, plural string) *apiextv1beta1.CustomResourceDefinition {
|
||||||
|
|
Loading…
Reference in New Issue