mirror of
https://github.com/hanzoai/compute.git
synced 2026-10-30 14:50:41 +00:00
The fleet showed standalone droplets but never the WORKER NODES of a DOKS cluster: ListNodePools already fetched each pool's Nodes (with real DropletID) but SyncNodePoolsCloud discarded the per-node list, and visor never called godo's Kubernetes.List/Get. So a cluster's nodes were invisible on world.hanzo.ai even though each is a real droplet. Source the nodes and surface them as fleet Machines: - service/doks.go: ListClusters(ctx) (Kubernetes.List + per-cluster Get for authoritative region/status/pools), nodePoolMachines (the ONE node->Machine mapper: Id=DropletID so it dedups against the droplet list, Name=node.Name, Provider=DigitalOcean, Size=pool.Size, Region=cluster.RegionSlug, State=node.Status, Tag=doks-cluster:<name>, IPs honestly empty; a node still provisioning with no DropletID is skipped), and the two org associations that feed it. - service/compute.go ListOrgKubernetesNodes: HOUSE account, cluster hanzo-org tag association (DOKS node droplets carry k8s tags, not a hanzo-org DROPLET tag, so they never surfaced via ListOrgMachines). - object/machine_cloud.go GetKubernetesNodesCloud: BYOC providers, the Provider.ClusterID association (mirrors SyncNodePoolsCloud's DO selection). - controllers/compute.go ListComputeKubernetesNodes: org-scoped, unions BOTH associations deduped by id-or-name (a cluster found via tag AND provider, or a node whose droplet is also listed, appears once) in the casibase envelope. - routers/router.go: GET /v1/kubernetes-nodes. Both associations implemented and unioned rather than guessing which is live — strictly more correct. Tests: service TestNodePoolMachines (mapping + skip unprovisioned + empty IPs) passes; controllers TestUnionMachinesDedup compiles and pins the dedup contract. (The object/controllers test BINARIES currently panic at init on origin/main — modernc.org/sqlite vs hanzoai/sqlite both register driver "sqlite" via object -> hanzoai/vfs/replica -> modernc, a pre-existing PR #10 migration artifact unrelated to this change; go build ./... and go vet are green.)
222 lines
6.2 KiB
Go
222 lines
6.2 KiB
Go
// Copyright 2025 Hanzo Industries Inc. All Rights Reserved.
|
|
//
|
|
// 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 object
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hanzoai/visor/service"
|
|
)
|
|
|
|
// GetKubernetesNodesCloud returns DOKS worker nodes — as service.Machines — for
|
|
// every active BYOC DigitalOcean provider that names a cluster (Provider.ClusterID).
|
|
// This is the Provider-record cluster→org association; the house-account tag
|
|
// association lives in service.ListOrgKubernetesNodes and the controller unions
|
|
// both, so a cluster discovered by either path surfaces its nodes (deduped by
|
|
// droplet id) exactly once. The DO provider selection mirrors SyncNodePoolsCloud
|
|
// (Type=="DigitalOcean" && ClusterID!=""), so nodes come from the same clusters
|
|
// visor already reconciles pools for.
|
|
func GetKubernetesNodesCloud(owner string) ([]*service.Machine, error) {
|
|
providers, err := getActiveCloudProviders(owner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var machines []*service.Machine
|
|
for _, provider := range providers {
|
|
if provider.Type != "DigitalOcean" || provider.ClusterID == "" {
|
|
continue
|
|
}
|
|
token := provider.ClientSecret
|
|
if token == "" {
|
|
token = provider.ClientId
|
|
}
|
|
client, err := service.NewDOKSClient(token, provider.ClusterID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nodes, err := client.NodeMachines(context.Background())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
machines = append(machines, nodes...)
|
|
}
|
|
return machines, nil
|
|
}
|
|
|
|
func getMachineFromService(owner string, provider string, clientMachine *service.Machine) *Machine {
|
|
return &Machine{
|
|
Owner: owner,
|
|
Name: clientMachine.Name,
|
|
Id: clientMachine.Id,
|
|
Provider: provider,
|
|
CreatedTime: clientMachine.CreatedTime,
|
|
UpdatedTime: clientMachine.UpdatedTime,
|
|
ExpireTime: clientMachine.ExpireTime,
|
|
DisplayName: clientMachine.DisplayName,
|
|
Region: clientMachine.Region,
|
|
Zone: clientMachine.Zone,
|
|
Category: clientMachine.Category,
|
|
Type: clientMachine.Type,
|
|
Size: clientMachine.Size,
|
|
Tag: clientMachine.Tag,
|
|
State: clientMachine.State,
|
|
Image: clientMachine.Image,
|
|
Os: clientMachine.Os,
|
|
PublicIp: clientMachine.PublicIp,
|
|
PrivateIp: clientMachine.PrivateIp,
|
|
CpuSize: clientMachine.CpuSize,
|
|
MemSize: clientMachine.MemSize,
|
|
}
|
|
}
|
|
|
|
func getMachinesCloud(owner string) ([]*Machine, error) {
|
|
machines := []*Machine{}
|
|
providers, err := getActiveCloudProviders(owner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, provider := range providers {
|
|
client, err2 := service.NewMachineClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.Region)
|
|
if err2 != nil {
|
|
return nil, err2
|
|
}
|
|
|
|
clientMachines, err2 := client.GetMachines()
|
|
if err2 != nil {
|
|
if provider.Type != "VMware" {
|
|
return nil, err2
|
|
}
|
|
}
|
|
|
|
for _, clientMachine := range clientMachines {
|
|
machine := getMachineFromService(owner, provider.Name, clientMachine)
|
|
machines = append(machines, machine)
|
|
}
|
|
}
|
|
|
|
return machines, nil
|
|
}
|
|
|
|
func SyncMachinesCloud(owner string) (bool, error) {
|
|
machines, err := getMachinesCloud(owner)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
dbMachines, err := GetMachines(owner)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
dbMachineMap := map[string]*Machine{}
|
|
for _, dbMachine := range dbMachines {
|
|
dbMachineMap[dbMachine.GetId()] = dbMachine
|
|
}
|
|
|
|
for _, machine := range machines {
|
|
if dbMachine, ok := dbMachineMap[machine.GetId()]; ok {
|
|
machine.RemoteProtocol = dbMachine.RemoteProtocol
|
|
machine.RemotePort = dbMachine.RemotePort
|
|
machine.RemoteUsername = dbMachine.RemoteUsername
|
|
machine.RemotePassword = dbMachine.RemotePassword
|
|
}
|
|
}
|
|
|
|
_, err = deleteMachines(owner)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if len(machines) == 0 {
|
|
return false, nil
|
|
}
|
|
|
|
affected, err := addMachines(owner, machines)
|
|
return affected, err
|
|
}
|
|
|
|
// CreateMachineCloud launches a new VM via the cloud provider and registers it in the DB.
|
|
func CreateMachineCloud(owner string, providerName string, spec *service.CreateMachineSpec) (*Machine, error) {
|
|
provider, err := getProvider(owner, providerName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if provider == nil {
|
|
return nil, fmt.Errorf("provider %q not found for owner %q", providerName, owner)
|
|
}
|
|
|
|
client, err := service.NewMachineClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.Region)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create machine client: %w", err)
|
|
}
|
|
|
|
clientMachine, err := client.CreateMachine(spec)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create cloud machine: %w", err)
|
|
}
|
|
|
|
machine := getMachineFromService(owner, providerName, clientMachine)
|
|
machine.Os = spec.OS
|
|
|
|
// Set remote access defaults based on OS
|
|
switch spec.OS {
|
|
case "windows":
|
|
machine.RemoteProtocol = "RDP"
|
|
machine.RemotePort = 3389
|
|
case "macos":
|
|
machine.RemoteProtocol = "VNC"
|
|
machine.RemotePort = 5900
|
|
default:
|
|
machine.RemoteProtocol = "SSH"
|
|
machine.RemotePort = 22
|
|
}
|
|
|
|
// Persist to DB
|
|
_, err = AddMachine(machine)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("machine created in cloud but DB insert failed: %w", err)
|
|
}
|
|
|
|
return machine, nil
|
|
}
|
|
|
|
func updateMachineCloud(oldMachine *Machine, machine *Machine) (bool, error) {
|
|
provider, err := getProvider(oldMachine.Owner, oldMachine.Provider)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if provider == nil {
|
|
return false, fmt.Errorf("The provider: %s does not exist", machine.Provider)
|
|
}
|
|
|
|
client, err := service.NewMachineClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.Region)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if oldMachine.State != machine.State {
|
|
affected, _, err := client.UpdateMachineState(oldMachine.Name, machine.State)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return affected, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|