Skip to content
This repository has been archived by the owner on Jan 3, 2023. It is now read-only.

Commit

Permalink
Merge pull request #91 from G-Harmon/mciList
Browse files Browse the repository at this point in the history
kubemci list command: look for relevant global forwarding rules.
  • Loading branch information
nikhiljindal authored Dec 12, 2017
2 parents dc1727b + 6dcc5b2 commit 935762c
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 6 deletions.
1 change: 1 addition & 0 deletions app/kubemci/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func NewCommand(in io.Reader, out, err io.Writer) *cobra.Command {
NewCmdDelete(out, err),
NewCmdGetStatus(out, err),
NewCmdGetVersion(out, err),
newCmdList(out, err),
)
rootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine)
return rootCmd
Expand Down
2 changes: 0 additions & 2 deletions app/kubemci/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ func addCreateFlags(cmd *cobra.Command, options *CreateOptions) error {
cmd.Flags().BoolVarP(&options.ForceUpdate, "force", "f", options.ForceUpdate, "[optional] overwrite existing settings if they are different")
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", options.Namespace, "[optional] namespace for the ingress only if left unspecified by ingress spec")
cmd.Flags().StringVarP(&options.StaticIPName, "static-ip", "", options.StaticIPName, "[optional] Global Static IP name to use only if left unspecified by ingress spec")
// TODO Add a verbose flag that turns on glog logging, or figure out how
// to accept glog flags in addition to the cobra flags.
return nil
}

Expand Down
98 changes: 98 additions & 0 deletions app/kubemci/cmd/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2017 Google, Inc.
//
// 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.

package cmd

import (
"fmt"
"io"

"github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/cloudinterface"
gcplb "github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/loadbalancer"
"github.com/spf13/cobra"
)

var (
listShortDesc = "List multicluster ingresses."
listLongDesc = `List multicluster ingresses.
List multicluster ingresses found in the given GCP project. Searches for MCIs based on naming convention.
`
)

type listOptions struct {
// Name of the GCP project in which the load balancer should be configured.
// Required
// TODO(nikhiljindal): This should be optional. Figure it out from gcloud settings.
GCPProject string
}

func newCmdList(out, err io.Writer) *cobra.Command {
var options listOptions
cmd := &cobra.Command{
Use: "list",
Short: listShortDesc,
Long: listLongDesc,
Run: func(cmd *cobra.Command, args []string) {
if err := validateListArgs(&options, args); err != nil {
fmt.Println(err)
return
}
if err := runList(&options, args); err != nil {
fmt.Println("Error listing load balancers:", err)
return
}
},
}
addListFlags(cmd, &options)
return cmd
}

func addListFlags(cmd *cobra.Command, options *listOptions) error {
cmd.Flags().StringVarP(&options.GCPProject, "gcp-project", "", options.GCPProject, "[required] name of the gcp project")
return nil
}

func validateListArgs(options *listOptions, args []string) error {
// Verify that the required flag is not missing.
if options.GCPProject == "" {
return fmt.Errorf("unexpected missing argument gcp-project.")
}
if len(args) != 0 {
return fmt.Errorf("unexpected args: %v. Expected 0 arguments.", args)
}
return nil
}

// runList prints a list of all mcis that we've created.
func runList(options *listOptions, args []string) error {
cloudInterface, err := cloudinterface.NewGCECloudInterface(options.GCPProject)
if err != nil {
fmt.Println("error creating cloud interface:", err)
return fmt.Errorf("error in creating cloud interface: %s", err)
}

lbs, err := gcplb.NewLoadBalancerSyncer("" /*lbName*/, nil /* clientset */, cloudInterface, options.GCPProject)
if err != nil {
fmt.Println("Error creating load balancer syncer:", err)
return err
}
balancers, err := lbs.ListLoadBalancers()
if err != nil {
fmt.Println("Error getting list of load balancers:", err)
return err
}
fmt.Println(balancers)
return nil
}
41 changes: 41 additions & 0 deletions app/kubemci/cmd/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2017 Google Inc.
//
// 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.

package cmd

import (
"testing"
)

func TestValidateListArgs(t *testing.T) {
// It should return an error with extra args.
options := listOptions{}
if err := validateListArgs(&options, []string{"arg1"}); err == nil {
t.Errorf("Expected error for non-empty args")
}

// It should return an error with missing project.
options = listOptions{}
if err := validateListArgs(&options, []string{}); err == nil {
t.Errorf("Expected error for non-empty args")
}

// validateListArgs should succeed with a project and empty args.
options = listOptions{
GCPProject: "myGcpProject",
}
if err := validateListArgs(&options, []string{}); err != nil {
t.Errorf("unexpected error from validateListArgs: %s", err)
}
}
4 changes: 2 additions & 2 deletions app/kubemci/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
)

var (
versionShortDescription = "Prints the version of this tool"
versionLongDescription = `Prints the version of this tool
versionShortDescription = "Print the version of this tool."
versionLongDescription = `Print the version of this tool.
Release builds have versions of the form x.y.z.
version x.y.z+ indicates that the client has some changes over the x.y.z release.
Expand Down
13 changes: 13 additions & 0 deletions app/kubemci/pkg/gcp/forwardingrule/fake_forwardingrulesyncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,16 @@ func (f *FakeForwardingRuleSyncer) GetLoadBalancerStatus(lbName string) (*status
}
return nil, fmt.Errorf("load balancer %s does not exist", lbName)
}

func (f *FakeForwardingRuleSyncer) ListLoadBalancerStatuses() ([]status.LoadBalancerStatus, error) {
var ret []status.LoadBalancerStatus
for _, fr := range f.EnsuredForwardingRules {
status := status.LoadBalancerStatus{
LoadBalancerName: fr.LBName,
Clusters: fr.Clusters,
IPAddress: fr.IPAddress,
}
ret = append(ret, status)
}
return ret, nil
}
34 changes: 34 additions & 0 deletions app/kubemci/pkg/gcp/forwardingrule/forwardingrulesyncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
"net/http"
"reflect"
"sort"
"strings"

compute "google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"

"github.com/golang/glog"
multierror "github.com/hashicorp/go-multierror"
"k8s.io/apimachinery/pkg/util/diff"
ingresslb "k8s.io/ingress-gce/pkg/loadbalancers"
"k8s.io/ingress-gce/pkg/utils"
Expand Down Expand Up @@ -123,6 +125,38 @@ func (s *ForwardingRuleSyncer) GetLoadBalancerStatus(lbName string) (*status.Loa
return status, nil
}

func (s *ForwardingRuleSyncer) ListLoadBalancerStatuses() ([]status.LoadBalancerStatus, error) {
var rules *compute.ForwardingRuleList
var err error
result := []status.LoadBalancerStatus{}
// TODO: When we have HTTPS ingresses, check for those as well.
if rules, err = s.frp.ListGlobalForwardingRules(); err != nil {
err = fmt.Errorf("Error getting global forwarding rules: %s", err)
fmt.Println(err)
return result, err
}
if rules == nil {
// This is being defensive.
err = fmt.Errorf("Unexpected nil from ListGlobalForwardingRules.")
fmt.Println(err)
return result, err
}
glog.V(5).Infof("rules: %+v", rules)
for _, item := range rules.Items {
if strings.HasPrefix(item.Name, "mci1") {
if lbStatus, decodeErr := status.FromString(item.Description); decodeErr != nil {
decodeErr = fmt.Errorf("Error decoding load balancer status: %s", decodeErr)
fmt.Println(decodeErr)
err = multierror.Append(err, decodeErr)
} else {
result = append(result, *lbStatus)
}

}
}
return result, err
}

func (s *ForwardingRuleSyncer) updateForwardingRule(existingFR, desiredFR *compute.ForwardingRule) error {
name := desiredFR.Name
// We do not have an UpdateForwardingRule method.
Expand Down
102 changes: 101 additions & 1 deletion app/kubemci/pkg/gcp/forwardingrule/forwardingrulesyncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestEnsureHttpForwardingRule(t *testing.T) {
// Verify that GET does not return NotFound and the returned forwarding rule is as expected.
fr, err := frp.GetGlobalForwardingRule(frName)
if err != nil {
t.Errorf("expected nil error, actual: %v", err)
t.Errorf("expected nil error getting forwarding rules, actual: %v", err)
}
if fr.IPAddress != c.expectedIpAddr {
t.Errorf("unexpected ip address. expected: %s, got: %s", c.expectedIpAddr, fr.IPAddress)
Expand All @@ -115,6 +115,106 @@ func TestEnsureHttpForwardingRule(t *testing.T) {
}
}

func TestListLoadBalancerStatuses(t *testing.T) {
lbName := "lb-name"
// Should create the forwarding rule as expected.
frp := ingresslb.NewFakeLoadBalancers("" /*name*/, nil /*namer*/)
namer := utilsnamer.NewNamer("mci1", lbName)
frs := NewForwardingRuleSyncer(namer, frp)
ipAddr := "1.2.3.4"
tpLink := "fakeLink"

clusters := []string{"cluster1", "cluster2", "cluster3"}

if err := frs.EnsureHttpForwardingRule(lbName, ipAddr, tpLink, clusters, false /*force*/); err != nil {
t.Fatalf("expected no error in ensuring forwarding rule, actual: %v", err)
}

list, err := frs.ListLoadBalancerStatuses()
if err != nil {
t.Errorf("unexpected error listing load balancer statuses: %v", err)
}
for _, rule := range list {
if rule.LoadBalancerName != lbName {
t.Errorf("Unexpected name. expected: %s, acctual: %s", lbName, rule.LoadBalancerName)
}
if rule.IPAddress != ipAddr {
t.Errorf("Unexpected IP addr. expected %s, actual: %s", ipAddr, rule.IPAddress)
}
actualClusters := mapFromSlice(rule.Clusters)
for _, expected := range clusters {
if _, ok := actualClusters[expected]; !ok {
t.Errorf("cluster %s is missing from resultant cluster list %v", expected, rule.Clusters)
}
}
}
}

func mapFromSlice(strs []string) map[string]struct{} {
mymap := make(map[string]struct{})
for _, s := range strs {
mymap[s] = struct{}{}
}
return mymap
}

func TestListLoadBalancersWithSkippedRules(t *testing.T) {
lbName := "lb-name"
// Should create the forwarding rule as expected.
frp := ingresslb.NewFakeLoadBalancers("" /*name*/, nil /*namer*/)
namer := utilsnamer.NewNamer("mci1", lbName)
frs := NewForwardingRuleSyncer(namer, frp)
ipAddr := "1.2.3.4"
tpLink := "fakeLink"

clusters := []string{"cluster1", "cluster2", "cluster3", "fooCluster"}

if err := frs.EnsureHttpForwardingRule(lbName, ipAddr, tpLink, clusters, false /*force*/); err != nil {
t.Fatalf("expected no error in ensuring forwarding rule, actual: %v", err)
}

// Add anoter forwarding rule that does *not* match our normal prefix.
namer = utilsnamer.NewNamer("otherFR", "blah")
frs = NewForwardingRuleSyncer(namer, frp)
if err := frs.EnsureHttpForwardingRule(lbName, ipAddr, tpLink, clusters, false /*force*/); err != nil {
t.Fatalf("expected no error in ensuring forwarding rule, actual: %v", err)
}

list, err := frs.ListLoadBalancerStatuses()
if err != nil {
t.Errorf("unexpected error listing load balancer statuses: %v", err)
}
for _, rule := range list {
if rule.LoadBalancerName != lbName {
t.Errorf("Unexpected name. expected: %s, acctual: %s", lbName, rule.LoadBalancerName)
}
if rule.IPAddress != ipAddr {
t.Errorf("Unexpected IP addr. expected %s, actual: %s", ipAddr, rule.IPAddress)
}
actualClusters := mapFromSlice(rule.Clusters)
for _, expected := range clusters {
if _, ok := actualClusters[expected]; !ok {
t.Errorf("cluster %s is missing from resultant cluster list %v", expected, rule.Clusters)
}
}
}
}

func TestListLoadBalancersNoForwardingRules(t *testing.T) {
lbName := "lb-name"
frp := ingresslb.NewFakeLoadBalancers("" /*name*/, nil /*namer*/)
namer := utilsnamer.NewNamer("mci1", lbName)
frs := NewForwardingRuleSyncer(namer, frp)

list, err := frs.ListLoadBalancerStatuses()
if err != nil {
t.Errorf("unexpected error listing load balancer statuses: %v", err)
}
if len(list) != 0 {
t.Errorf("Expected no fowarding rules. Actual: %v", list)
}
}

func TestDeleteForwardingRule(t *testing.T) {
lbName := "lb-name"
ipAddr := "1.2.3.4"
Expand Down
2 changes: 2 additions & 0 deletions app/kubemci/pkg/gcp/forwardingrule/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ type ForwardingRuleSyncerInterface interface {
DeleteForwardingRules() error
// GetLoadBalancerStatus returns the struct describing the status of the given load balancer.
GetLoadBalancerStatus(lbName string) (*status.LoadBalancerStatus, error)
// ListLoadBalancerStatuses returns status of all MCI ingresses (load balancers).
ListLoadBalancerStatuses() ([]status.LoadBalancerStatus, error)
}
Loading

0 comments on commit 935762c

Please sign in to comment.