package controller import ( "fmt" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "github.com/openshift/origin/pkg/security" "github.com/openshift/origin/pkg/security/mcs" "github.com/openshift/origin/pkg/security/uid" "github.com/openshift/origin/pkg/security/uidallocator" ) type MCSAllocationFunc func(uid.Block) *mcs.Label // DefaultMCSAllocation returns a label from the MCS range that matches the offset // within the overall range. blockSize must be a positive integer representing the // number of labels to jump past in the category space (if 1, range == label, if 2 // each range will have two labels). func DefaultMCSAllocation(from *uid.Range, to *mcs.Range, blockSize int) MCSAllocationFunc { return func(block uid.Block) *mcs.Label { ok, offset := from.Offset(block) if !ok { return nil } if blockSize > 0 { offset = offset * uint32(blockSize) } label, _ := to.LabelAt(uint64(offset)) return label } } type Allocation struct { uid uidallocator.Interface mcs MCSAllocationFunc client kcoreclient.NamespaceInterface } // retryCount is the number of times to retry on a conflict when updating a namespace const retryCount = 2 // Next processes a changed namespace and tries to allocate a uid range for it. If it is // successful, an mcs label corresponding to the relative position of the range is also // set. func (c *Allocation) Next(ns *kapi.Namespace) error { tx := &tx{} defer tx.Rollback() if _, ok := ns.Annotations[security.UIDRangeAnnotation]; ok { return nil } if ns.Annotations == nil { ns.Annotations = make(map[string]string) } // do uid allocation block, err := c.uid.AllocateNext() if err != nil { return err } tx.Add(func() error { return c.uid.Release(block) }) ns.Annotations[security.UIDRangeAnnotation] = block.String() ns.Annotations[security.SupplementalGroupsAnnotation] = block.String() if _, ok := ns.Annotations[security.MCSAnnotation]; !ok { if label := c.mcs(block); label != nil { ns.Annotations[security.MCSAnnotation] = label.String() } } // TODO: could use a client.GuaranteedUpdate/Merge function for i := 0; i < retryCount; i++ { _, err := c.client.Update(ns) if err == nil { // commit and exit tx.Commit() return nil } if errors.IsNotFound(err) { return nil } if !errors.IsConflict(err) { return err } newNs, err := c.client.Get(ns.Name) if errors.IsNotFound(err) { return nil } if err != nil { return err } if changedAndSetAnnotations(ns, newNs) { return nil } // try again if newNs.Annotations == nil { newNs.Annotations = make(map[string]string) } newNs.Annotations[security.UIDRangeAnnotation] = ns.Annotations[security.UIDRangeAnnotation] newNs.Annotations[security.SupplementalGroupsAnnotation] = ns.Annotations[security.SupplementalGroupsAnnotation] newNs.Annotations[security.MCSAnnotation] = ns.Annotations[security.MCSAnnotation] ns = newNs } return fmt.Errorf("unable to allocate security info on %q after %d retries", ns.Name, retryCount) } func changedAndSetAnnotations(old, ns *kapi.Namespace) bool { if value, ok := ns.Annotations[security.UIDRangeAnnotation]; ok && value != old.Annotations[security.UIDRangeAnnotation] { return true } if value, ok := ns.Annotations[security.MCSAnnotation]; ok && value != old.Annotations[security.MCSAnnotation] { return true } if value, ok := ns.Annotations[security.SupplementalGroupsAnnotation]; ok && value != old.Annotations[security.SupplementalGroupsAnnotation] { return true } return false } type tx struct { rollback []func() error } func (tx *tx) Add(fn func() error) { tx.rollback = append(tx.rollback, fn) } func (tx *tx) HasChanges() bool { return len(tx.rollback) > 0 } func (tx *tx) Rollback() { for _, fn := range tx.rollback { if err := fn(); err != nil { utilruntime.HandleError(fmt.Errorf("unable to undo tx: %v", err)) } } } func (tx *tx) Commit() { tx.rollback = nil }