Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions cloudmock/gce/mock_gce_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/storage/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"k8s.io/kops/cloudmock/gce/mockcloudresourcemanager"
mockcompute "k8s.io/kops/cloudmock/gce/mockcompute"
"k8s.io/kops/cloudmock/gce/mockdns"
Expand Down Expand Up @@ -71,15 +70,12 @@ func (c *MockGCECloud) AllResources() map[string]interface{} {
return c.computeClient.AllResources()
}

// GetCloudGroups is not implemented yet
func (c *MockGCECloud) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
klog.V(8).Infof("MockGCECloud cloud provider GetCloudGroups not implemented yet")
return nil, fmt.Errorf("MockGCECloud cloud provider does not support getting cloud groups at this time")
func (c *MockGCECloud) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, options *fi.GetCloudGroupsOptions, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
return gce.GetCloudGroups(c, cluster, instancegroups, options, nodes)
}

// Zones is not implemented yet
func (c *MockGCECloud) Zones() ([]string, error) {
return nil, fmt.Errorf("not yet implemented")
return gce.GetZones(c)
}

// WithLabels returns a copy of the MockGCECloud bound to the specified labels
Expand Down Expand Up @@ -170,8 +166,7 @@ func (c *MockGCECloud) Labels() map[string]string {

// DeleteGroup implements fi.Cloud::DeleteGroup
func (c *MockGCECloud) DeleteGroup(g *cloudinstances.CloudInstanceGroup) error {
return nil
// return deleteCloudInstanceGroup(c, g)
return gce.DeleteCloudInstanceGroup(c, g)
}

// DeleteInstance deletes a GCE instance
Expand Down
9 changes: 6 additions & 3 deletions cloudmock/gce/mockcompute/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type MockClient struct {
addressClient *addressClient
firewallClient *firewallClient
routerClient *routerClient
instanceClient *instanceClient

instanceTemplateClient *instanceTemplateClient
instanceGroupManagerClient *instanceGroupManagerClient
Expand All @@ -49,6 +50,8 @@ var _ gce.ComputeClient = &MockClient{}

// NewMockClient creates a new mock client.
func NewMockClient(project string) *MockClient {
instanceClient := newInstanceClient()

return &MockClient{
projectClient: newProjectClient(project),
zoneClient: newZoneClient(project),
Expand All @@ -65,7 +68,8 @@ func NewMockClient(project string) *MockClient {
routerClient: newRouterClient(),

instanceTemplateClient: newInstanceTemplateClient(),
instanceGroupManagerClient: newInstanceGroupManagerClient(),
instanceGroupManagerClient: newInstanceGroupManagerClient(instanceClient),
instanceClient: instanceClient,
targetPoolClient: newTargetPoolClient(),

diskClient: newDiskClient(),
Expand Down Expand Up @@ -158,8 +162,7 @@ func (c *MockClient) Routers() gce.RouterClient {
}

func (c *MockClient) Instances() gce.InstanceClient {
// Not implemented.
return nil
return c.instanceClient
}

func (c *MockClient) InstanceTemplates() gce.InstanceTemplateClient {
Expand Down
124 changes: 124 additions & 0 deletions cloudmock/gce/mockcompute/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Copyright 2026 The Kubernetes Authors.

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 mockcompute

import (
"context"
"fmt"
"sync"

compute "google.golang.org/api/compute/v1"
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
)

type instanceClient struct {
// instances are instances keyed by project, zone, and name.
instances map[string]map[string]map[string]*compute.Instance
sync.Mutex
}

var _ gce.InstanceClient = &instanceClient{}

func newInstanceClient() *instanceClient {
return &instanceClient{
instances: map[string]map[string]map[string]*compute.Instance{},
}
}

func (c *instanceClient) All() map[string]interface{} {
return nil
}

func (c *instanceClient) Insert(project, zone string, instance *compute.Instance) (*compute.Operation, error) {
c.Lock()
defer c.Unlock()
zones, ok := c.instances[project]
if !ok {
zones = map[string]map[string]*compute.Instance{}
c.instances[project] = zones
}
instances, ok := zones[zone]
if !ok {
instances = map[string]*compute.Instance{}
zones[zone] = instances
}
instance.SelfLink = instance.Name
instances[instance.Name] = instance

return doneOperation(), nil
}

func (c *instanceClient) Delete(project, zone, name string) (*compute.Operation, error) {
c.Lock()
defer c.Unlock()

zones, ok := c.instances[project]
if !ok {
return nil, notFoundError()
}
instances, ok := zones[zone]
if !ok {
return nil, notFoundError()
}
if _, ok := instances[name]; !ok {
return nil, notFoundError()
}
delete(instances, name)
return doneOperation(), nil
}

func (c *instanceClient) Get(project, zone, name string) (*compute.Instance, error) {
c.Lock()
defer c.Unlock()
zones, ok := c.instances[project]
if !ok {
return nil, notFoundError()
}
res, ok := zones[zone]
if !ok {
return nil, notFoundError()
}
igm, ok := res[name]
if !ok {
return nil, notFoundError()
}
return igm, nil
}

func (c *instanceClient) List(ctx context.Context, project, zone string) ([]*compute.Instance, error) {
c.Lock()
defer c.Unlock()

zones, ok := c.instances[project]
if !ok {
return nil, nil
}
instances, ok := zones[zone]
if !ok {
return nil, nil
}

var l []*compute.Instance
for _, instance := range instances {
l = append(l, instance)
}
return l, nil
}

func (c *instanceClient) SetMetadata(project, zone, name string, metadata *compute.Metadata) (*compute.Operation, error) {
return nil, fmt.Errorf("setmetadata unimplemented")
}
58 changes: 50 additions & 8 deletions cloudmock/gce/mockcompute/instance_group_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,20 @@ import (
type instanceGroupManagerClient struct {
// instanceGroupManagers are instanceGroupManagers keyed by project, zone, and name.
instanceGroupManagers map[string]map[string]map[string]*compute.InstanceGroupManager
// managedInstances are managedInstances keyed by project, zone, and name.
managedInstances map[string]map[string]map[string]*compute.ManagedInstance
// instanceClient is the client for instances.
instanceClient gce.InstanceClient
sync.Mutex
}

var _ gce.InstanceGroupManagerClient = &instanceGroupManagerClient{}

func newInstanceGroupManagerClient() *instanceGroupManagerClient {
func newInstanceGroupManagerClient(instanceClient gce.InstanceClient) *instanceGroupManagerClient {
return &instanceGroupManagerClient{
instanceGroupManagers: map[string]map[string]map[string]*compute.InstanceGroupManager{},
managedInstances: map[string]map[string]map[string]*compute.ManagedInstance{},
instanceClient: instanceClient,
}
}

Expand All @@ -56,18 +62,39 @@ func (c *instanceGroupManagerClient) All() map[string]interface{} {
func (c *instanceGroupManagerClient) Insert(project, zone string, igm *compute.InstanceGroupManager) (*compute.Operation, error) {
c.Lock()
defer c.Unlock()
zones, ok := c.instanceGroupManagers[project]
igmZones, ok := c.instanceGroupManagers[project]
if !ok {
zones = map[string]map[string]*compute.InstanceGroupManager{}
c.instanceGroupManagers[project] = zones
igmZones = map[string]map[string]*compute.InstanceGroupManager{}
c.instanceGroupManagers[project] = igmZones
}
igms, ok := zones[zone]
igms, ok := igmZones[zone]
if !ok {
igms = map[string]*compute.InstanceGroupManager{}
zones[zone] = igms
igmZones[zone] = igms
}
igm.SelfLink = fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instanceGroupManagers/%s", project, zone, igm.Name)
igms[igm.Name] = igm

newInstance := &compute.Instance{
Name: igm.Name,
}

c.instanceClient.Insert(project, zone, newInstance)

instanceZones, ok := c.managedInstances[project]
if !ok {
instanceZones = map[string]map[string]*compute.ManagedInstance{}
c.managedInstances[project] = instanceZones
}
_, ok = instanceZones[zone]
if !ok {
instanceZones[zone] = map[string]*compute.ManagedInstance{}
}

c.managedInstances[project][zone][igm.Name] = &compute.ManagedInstance{
Instance: fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s", project, zone, igm.Name),
}

return doneOperation(), nil
}

Expand Down Expand Up @@ -126,8 +153,23 @@ func (c *instanceGroupManagerClient) List(ctx context.Context, project, zone str
}

func (c *instanceGroupManagerClient) ListManagedInstances(ctx context.Context, project, zone, name string) ([]*compute.ManagedInstance, error) {
var instances []*compute.ManagedInstance
return instances, nil
c.Lock()
defer c.Unlock()

zones, ok := c.managedInstances[project]
if !ok {
return nil, nil
}
instances, ok := zones[zone]
if !ok {
return nil, nil
}

var l []*compute.ManagedInstance
for _, instance := range instances {
l = append(l, instance)
}
return l, nil
}

func (c *instanceGroupManagerClient) RecreateInstances(project, zone, name, id string) (*compute.Operation, error) {
Expand Down
5 changes: 3 additions & 2 deletions cmd/kops/delete_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"k8s.io/kops/pkg/instancegroups"
"k8s.io/kops/pkg/kubeconfig"
"k8s.io/kops/pkg/validation"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
Expand Down Expand Up @@ -211,7 +212,7 @@ func RunDeleteInstance(ctx context.Context, f *util.Factory, out io.Writer, opti
return err
}

groups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes)
groups, err := cloud.GetCloudGroups(cluster, instanceGroups, &fi.GetCloudGroupsOptions{}, nodes)
if err != nil {
return err
}
Expand Down Expand Up @@ -361,7 +362,7 @@ func completeInstanceOrNode(f commandutils.Factory, options *DeleteInstanceOptio
return commandutils.CompletionError("initializing cloud", err)
}

groups, err := cloud.GetCloudGroups(cluster, instanceGroups, false, nodes)
groups, err := cloud.GetCloudGroups(cluster, instanceGroups, &fi.GetCloudGroupsOptions{}, nodes)
if err != nil {
return commandutils.CompletionError("listing instances", err)
}
Expand Down
4 changes: 3 additions & 1 deletion cmd/kops/delete_instancegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type DeleteInstanceGroupOptions struct {
Yes bool
ClusterName string
GroupName string
Force bool
}

func NewCmdDeleteInstanceGroup(f *util.Factory, out io.Writer) *cobra.Command {
Expand Down Expand Up @@ -114,6 +115,7 @@ func NewCmdDeleteInstanceGroup(f *util.Factory, out io.Writer) *cobra.Command {
}

cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Specify --yes to immediately delete the instance group")
cmd.Flags().BoolVarP(&options.Force, "force", "f", options.Force, "Specify --force to force the deletion of the instance group even if not all instances can be found.")

return cmd
}
Expand Down Expand Up @@ -181,7 +183,7 @@ func RunDeleteInstanceGroup(ctx context.Context, f *util.Factory, out io.Writer,
d.Cloud = cloud
d.Clientset = clientset

err = d.DeleteInstanceGroup(group)
err = d.DeleteInstanceGroup(group, options.Force)
if err != nil {
return err
}
Expand Down
Loading