package controller import ( "fmt" "time" kapi "k8s.io/kubernetes/pkg/api" kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" "k8s.io/kubernetes/pkg/registry/rangeallocation" utilruntime "k8s.io/kubernetes/pkg/util/runtime" utilwait "k8s.io/kubernetes/pkg/util/wait" "github.com/openshift/origin/pkg/security" "github.com/openshift/origin/pkg/security/uid" "github.com/openshift/origin/pkg/security/uidallocator" ) // Repair is a controller loop that periodically examines all UID allocations // and logs any errors, and then sets the compacted and accurate list of both // // Can be run at infrequent intervals, and is best performed on startup of the master. // Is level driven and idempotent - all claimed UIDs will be updated into the allocator // map at the end of a single execution loop if no race is encountered. // type Repair struct { interval time.Duration client kcoreclient.NamespaceInterface alloc rangeallocation.RangeRegistry uidRange *uid.Range } // NewRepair creates a controller that periodically ensures that all UIDs labels that are allocated in the cluster // are claimed. func NewRepair(interval time.Duration, client kcoreclient.NamespaceInterface, uidRange *uid.Range, alloc rangeallocation.RangeRegistry) *Repair { return &Repair{ interval: interval, client: client, uidRange: uidRange, alloc: alloc, } } // RunUntil starts the controller until the provided ch is closed. func (c *Repair) RunUntil(ch chan struct{}) { utilwait.Until(func() { if err := c.RunOnce(); err != nil { utilruntime.HandleError(err) } }, c.interval, ch) } // RunOnce verifies the state of allocations and returns an error if an unrecoverable problem occurs. func (c *Repair) RunOnce() error { // TODO: (per smarterclayton) if Get() or List() is a weak consistency read, // or if they are executed against different leaders, // the ordering guarantee required to ensure no item is allocated twice is violated. // List must return a ResourceVersion higher than the etcd index Get, // and the release code must not release items that have allocated but not yet been created // See #8295 latest, err := c.alloc.Get() if err != nil { return fmt.Errorf("unable to refresh the security allocation UID blocks: %v", err) } list, err := c.client.List(kapi.ListOptions{}) if err != nil { return fmt.Errorf("unable to refresh the security allocation UID blocks: %v", err) } uids := uidallocator.NewInMemory(c.uidRange) for _, ns := range list.Items { value, ok := ns.Annotations[security.UIDRangeAnnotation] if !ok { continue } block, err := uid.ParseBlock(value) if err != nil { continue } switch err := uids.Allocate(block); err { case nil: case uidallocator.ErrNotInRange, uidallocator.ErrAllocated: continue case uidallocator.ErrFull: // TODO: send event return fmt.Errorf("the UID range %s is full; you must widen the range in order to allocate more UIDs", c.uidRange) default: return fmt.Errorf("unable to allocate UID block %s for namespace %s due to an unknown error, exiting: %v", block, ns.Name, err) } } err = uids.Snapshot(latest) if err != nil { return fmt.Errorf("unable to persist the updated namespace UID allocations: %v", err) } if err := c.alloc.CreateOrUpdate(latest); err != nil { return fmt.Errorf("unable to persist the updated namespace UID allocations: %v", err) } return nil }