package network
import (
"errors"
"fmt"
"io"
"reflect"
"strings"
"time"
"github.com/spf13/cobra"
kapi "k8s.io/kubernetes/pkg/api"
kapierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
kerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/wait"
osclient "github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
"github.com/openshift/origin/pkg/project/api"
sdnapi "github.com/openshift/origin/pkg/sdn/api"
)
type ProjectOptions struct {
DefaultNamespace string
Oclient *osclient.Client
Kclient *kclientset.Clientset
Out io.Writer
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
RESTClientFactory func(mapping *meta.RESTMapping) (resource.RESTClient, error)
ProjectNames []string
// Common optional params
Selector string
CheckSelector bool
}
func (p *ProjectOptions) Complete(f *clientcmd.Factory, c *cobra.Command, args []string, out io.Writer) error {
defaultNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
oc, _, kc, err := f.Clients()
if err != nil {
return err
}
mapper, typer := f.Object(false)
p.DefaultNamespace = defaultNamespace
p.Oclient = oc
p.Kclient = kc
p.Out = out
p.Mapper = mapper
p.Typer = typer
p.RESTClientFactory = f.Factory.ClientForMapping
p.ProjectNames = []string{}
if len(args) != 0 {
p.ProjectNames = append(p.ProjectNames, args...)
}
return nil
}
// Common validations
func (p *ProjectOptions) Validate() error {
errList := []error{}
if p.CheckSelector {
if len(p.Selector) > 0 {
if _, err := labels.Parse(p.Selector); err != nil {
errList = append(errList, errors.New("--selector=<project_selector> must be a valid label selector"))
}
}
if len(p.ProjectNames) != 0 {
errList = append(errList, errors.New("either specify --selector=<project_selector> or projects but not both"))
}
} else if len(p.ProjectNames) == 0 {
errList = append(errList, errors.New("must provide --selector=<project_selector> or projects"))
}
clusterNetwork, err := p.Oclient.ClusterNetwork().Get(sdnapi.ClusterNetworkDefault)
if err != nil {
if kapierrors.IsNotFound(err) {
errList = append(errList, errors.New("Managing pod network is only supported for openshift multitenant network plugin"))
} else {
errList = append(errList, errors.New("Failed to fetch current network plugin info"))
}
} else if !sdnapi.IsOpenShiftMultitenantNetworkPlugin(clusterNetwork.PluginName) {
errList = append(errList, fmt.Errorf("Using plugin: %q, managing pod network is only supported for openshift multitenant network plugin", clusterNetwork.PluginName))
}
return kerrors.NewAggregate(errList)
}
func (p *ProjectOptions) GetProjects() ([]*api.Project, error) {
nameArgs := []string{"projects"}
if len(p.ProjectNames) != 0 {
nameArgs = append(nameArgs, p.ProjectNames...)
}
r := resource.NewBuilder(p.Mapper, p.Typer, resource.ClientMapperFunc(p.RESTClientFactory), kapi.Codecs.UniversalDecoder()).
ContinueOnError().
NamespaceParam(p.DefaultNamespace).
SelectorParam(p.Selector).
ResourceTypeOrNameArgs(true, nameArgs...).
Flatten().
Do()
if r.Err() != nil {
return nil, r.Err()
}
errList := []error{}
projectList := []*api.Project{}
_ = r.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
project, ok := info.Object.(*api.Project)
if !ok {
err := fmt.Errorf("cannot convert input to Project: %v", reflect.TypeOf(info.Object))
errList = append(errList, err)
// Don't bail out if one project fails
return nil
}
projectList = append(projectList, project)
return nil
})
if len(errList) != 0 {
return projectList, kerrors.NewAggregate(errList)
}
if len(projectList) == 0 {
return projectList, fmt.Errorf("No projects found")
} else {
givenProjectNames := sets.NewString(p.ProjectNames...)
foundProjectNames := sets.String{}
for _, project := range projectList {
foundProjectNames.Insert(project.ObjectMeta.Name)
}
skippedProjectNames := givenProjectNames.Difference(foundProjectNames)
if skippedProjectNames.Len() > 0 {
return projectList, fmt.Errorf("Projects %v not found", strings.Join(skippedProjectNames.List(), ", "))
}
}
return projectList, nil
}
func (p *ProjectOptions) UpdatePodNetwork(nsName string, action sdnapi.PodNetworkAction, args string) error {
// Get corresponding NetNamespace for given namespace
netns, err := p.Oclient.NetNamespaces().Get(nsName)
if err != nil {
return err
}
// Apply pod network change intent
sdnapi.SetChangePodNetworkAnnotation(netns, action, args)
// Update NetNamespace object
_, err = p.Oclient.NetNamespaces().Update(netns)
if err != nil {
return err
}
// Validate SDN controller applied or rejected the intent
backoff := wait.Backoff{
Steps: 15,
Duration: 500 * time.Millisecond,
Factor: 1.1,
}
return wait.ExponentialBackoff(backoff, func() (bool, error) {
updatedNetNs, err := p.Oclient.NetNamespaces().Get(netns.NetName)
if err != nil {
return false, err
}
if _, _, err = sdnapi.GetChangePodNetworkAnnotation(updatedNetNs); err == sdnapi.ErrorPodNetworkAnnotationNotFound {
return true, nil
}
// Pod network change not applied yet
return false, nil
})
}