From 1f1eac4f085c0427a59c8a52157cc54b164099b0 Mon Sep 17 00:00:00 2001 From: Jayshan Raghunandan Date: Thu, 19 Mar 2026 05:20:55 +0900 Subject: [PATCH] feat: improve aio support for admin/volume ingress and fix UI links (#8679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: improve allInOne mode support for admin/volume ingress and fix master UI links - Add allInOne support to admin ingress template, matching the pattern used by filer and s3 ingress templates (or-based enablement with ternary service name selection) - Add allInOne support to volume ingress template, which previously required volume.enabled even when the volume server runs within the allInOne pod - Expose admin ports in allInOne deployment and service when allInOne.admin.enabled is set - Add allInOne.admin config section to values.yaml (enabled by default, ports inherit from admin.*) - Fix legacy master UI templates (master.html, masterNewRaft.html) to prefer PublicUrl over internal Url when linking to volume server UI. The new admin UI already handles this correctly. * fix: revert admin allInOne changes and fix PublicUrl in admin dashboard The admin binary (`weed admin`) is a separate process that cannot run inside `weed server` (allInOne mode). Revert the admin-related allInOne helm chart changes that caused 503 errors on admin ingress. Fix bug in cluster_topology.go where VolumeServer.PublicURL was set to node.Id (internal pod address) instead of the actual public URL. Add public_url field to DataNodeInfo proto message so the topology gRPC response carries the public URL set via -volume.publicUrl flag. Co-Authored-By: Claude Opus 4.6 * fix: use HTTP /dir/status to populate PublicUrl in admin dashboard The gRPC DataNodeInfo proto does not include PublicUrl, so the admin dashboard showed internal pod IPs instead of the configured public URL. Fetch PublicUrl from the master's /dir/status HTTP endpoint and apply it in both GetClusterTopology and GetClusterVolumeServers code paths. Also reverts the unnecessary proto field additions from the previous commit and cleans up a stray blank line in all-in-one-service.yml. * fix: apply PublicUrl link fix to masterNewRaft.html Match the same conditional logic already applied to master.html — prefer PublicUrl when set and different from Url. * fix: add HTTP timeout and status check to fetchPublicUrlMap Use a 5s-timeout client instead of http.DefaultClient to prevent blocking indefinitely when the master is unresponsive. Also check the HTTP status code before attempting to parse the response body. * fix: fall back to node address when PublicUrl is empty Prevents blank links in the admin dashboard when PublicUrl is not configured, such as in standalone or mixed-version clusters. * fix: log io.ReadAll error in fetchPublicUrlMap --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Chris Lu --- .../all-in-one/all-in-one-service.yml | 2 +- .../templates/volume/volume-ingress.yaml | 10 ++- weed/admin/dash/cluster_topology.go | 83 ++++++++++++++++++- weed/admin/dash/volume_management.go | 13 +++ weed/server/master_ui/master.html | 8 +- weed/server/master_ui/masterNewRaft.html | 8 +- 6 files changed, 113 insertions(+), 11 deletions(-) diff --git a/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml b/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml index f0747267d..f6859ad90 100644 --- a/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml +++ b/k8s/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml @@ -73,7 +73,7 @@ spec: targetPort: {{ .Values.allInOne.sftp.port | default .Values.sftp.port }} protocol: TCP {{- end }} - + # Server metrics port (single metrics endpoint for all services) {{- if .Values.allInOne.metricsPort }} - name: "server-metrics" diff --git a/k8s/charts/seaweedfs/templates/volume/volume-ingress.yaml b/k8s/charts/seaweedfs/templates/volume/volume-ingress.yaml index 83ef536cd..b36c29867 100644 --- a/k8s/charts/seaweedfs/templates/volume/volume-ingress.yaml +++ b/k8s/charts/seaweedfs/templates/volume/volume-ingress.yaml @@ -1,4 +1,8 @@ -{{- if and .Values.volume.enabled .Values.volume.ingress.enabled }} +{{- /* Volume ingress works for both normal mode (volume.enabled) and all-in-one mode (allInOne.enabled) */}} +{{- $volumeEnabled := or .Values.volume.enabled .Values.allInOne.enabled }} +{{- if and $volumeEnabled .Values.volume.ingress.enabled }} +{{- /* Determine service name based on deployment mode */}} +{{- $serviceName := ternary (include "seaweedfs.componentName" (list . "all-in-one")) (include "seaweedfs.componentName" (list . "volume")) .Values.allInOne.enabled }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} @@ -42,11 +46,11 @@ spec: backend: {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} service: - name: {{ include "seaweedfs.componentName" (list . "volume") }} + name: {{ $serviceName }} port: number: {{ .Values.volume.port }} {{- else }} - serviceName: {{ include "seaweedfs.componentName" (list . "volume") }} + serviceName: {{ $serviceName }} servicePort: {{ .Values.volume.port }} {{- end }} {{- end }} diff --git a/weed/admin/dash/cluster_topology.go b/weed/admin/dash/cluster_topology.go index aca29cd4a..3611251ff 100644 --- a/weed/admin/dash/cluster_topology.go +++ b/weed/admin/dash/cluster_topology.go @@ -2,13 +2,20 @@ package dash import ( "context" + "encoding/json" "fmt" + "io" + "net/http" "time" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" ) +var dirStatusClient = &http.Client{ + Timeout: 5 * time.Second, +} + // GetClusterTopology returns the current cluster topology with caching func (s *AdminServer) GetClusterTopology() (*ClusterTopology, error) { now := time.Now() @@ -35,8 +42,71 @@ func (s *AdminServer) GetClusterTopology() (*ClusterTopology, error) { return topology, nil } +// fetchPublicUrlMap queries the master's /dir/status HTTP endpoint and returns +// a map from data node ID (ip:port) to its PublicUrl. +func (s *AdminServer) fetchPublicUrlMap() map[string]string { + currentMaster := s.masterClient.GetMaster(context.Background()) + if currentMaster == "" { + return nil + } + + url := fmt.Sprintf("http://%s/dir/status", currentMaster.ToHttpAddress()) + resp, err := dirStatusClient.Get(url) + if err != nil { + glog.V(1).Infof("Failed to fetch /dir/status from %s: %v", currentMaster, err) + return nil + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + glog.V(1).Infof("Non-OK response from /dir/status: %d", resp.StatusCode) + return nil + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + glog.V(1).Infof("Failed to read /dir/status response body: %v", err) + return nil + } + + // Parse the JSON response to extract PublicUrl for each data node + var status struct { + Topology struct { + DataCenters []struct { + Racks []struct { + DataNodes []struct { + Url string `json:"Url"` + PublicUrl string `json:"PublicUrl"` + } `json:"DataNodes"` + } `json:"Racks"` + } `json:"DataCenters"` + } `json:"Topology"` + } + + if err := json.Unmarshal(body, &status); err != nil { + glog.V(1).Infof("Failed to parse /dir/status response: %v", err) + return nil + } + + publicUrls := make(map[string]string) + for _, dc := range status.Topology.DataCenters { + for _, rack := range dc.Racks { + for _, dn := range rack.DataNodes { + if dn.PublicUrl != "" { + publicUrls[dn.Url] = dn.PublicUrl + } + } + } + } + return publicUrls +} + // getTopologyViaGRPC gets topology using gRPC (original method) func (s *AdminServer) getTopologyViaGRPC(topology *ClusterTopology) error { + // Fetch public URL mapping from master HTTP API + // The gRPC DataNodeInfo does not include PublicUrl, so we supplement it. + publicUrls := s.fetchPublicUrlMap() + // Get cluster status from master err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) @@ -85,12 +155,23 @@ func (s *AdminServer) getTopologyViaGRPC(topology *ClusterTopology) error { } } + // Look up PublicUrl from master HTTP API + // Use node.Address (ip:port) as the key, matching the Url field in /dir/status + nodeAddr := node.Address + if nodeAddr == "" { + nodeAddr = node.Id + } + publicUrl := publicUrls[nodeAddr] + if publicUrl == "" { + publicUrl = nodeAddr + } + vs := VolumeServer{ ID: node.Id, Address: node.Id, DataCenter: dc.Id, Rack: rack.Id, - PublicURL: node.Id, + PublicURL: publicUrl, Volumes: int(totalVolumes), MaxVolumes: int(totalMaxVolumes), DiskUsage: totalSize, diff --git a/weed/admin/dash/volume_management.go b/weed/admin/dash/volume_management.go index 805e891d0..67b352733 100644 --- a/weed/admin/dash/volume_management.go +++ b/weed/admin/dash/volume_management.go @@ -413,6 +413,9 @@ func (s *AdminServer) VacuumVolume(volumeID int, server string) error { func (s *AdminServer) GetClusterVolumeServers() (*ClusterVolumeServersData, error) { var volumeServerMap map[string]*VolumeServer + // Fetch public URL mapping from master HTTP API + publicUrls := s.fetchPublicUrlMap() + // Make only ONE VolumeList call and use it for both topology building AND EC shard processing err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) @@ -436,8 +439,18 @@ func (s *AdminServer) GetClusterVolumeServers() (*ClusterVolumeServersData, erro for _, node := range rack.DataNodeInfos { // Initialize volume server if not exists if volumeServerMap[node.Id] == nil { + // Look up PublicUrl from master HTTP API + nodeAddr := node.Address + if nodeAddr == "" { + nodeAddr = node.Id + } + publicUrl := publicUrls[nodeAddr] + if publicUrl == "" { + publicUrl = nodeAddr + } volumeServerMap[node.Id] = &VolumeServer{ Address: node.Id, + PublicURL: publicUrl, DataCenter: dc.Id, Rack: rack.Id, Volumes: 0, diff --git a/weed/server/master_ui/master.html b/weed/server/master_ui/master.html index 40d49991b..ebfeeceb7 100644 --- a/weed/server/master_ui/master.html +++ b/weed/server/master_ui/master.html @@ -88,9 +88,11 @@ {{ $dc.Id }} {{ $rack.Id }} - {{ $dn.Url }} - {{ if ne $dn.PublicUrl $dn.Url }} - / {{ $dn.PublicUrl }} + + {{ if and (ne $dn.PublicUrl "") (ne $dn.PublicUrl $dn.Url) }} + {{ $dn.PublicUrl }} + {{ else }} + {{ $dn.Url }} {{ end }} {{ $dn.Volumes }} diff --git a/weed/server/master_ui/masterNewRaft.html b/weed/server/master_ui/masterNewRaft.html index 5f16d73a1..eabccbff4 100644 --- a/weed/server/master_ui/masterNewRaft.html +++ b/weed/server/master_ui/masterNewRaft.html @@ -101,9 +101,11 @@ {{ $dc.Id }} {{ $rack.Id }} - {{ $dn.Url }} - {{ if ne $dn.PublicUrl $dn.Url }} - / {{ $dn.PublicUrl }} + + {{ if and (ne $dn.PublicUrl "") (ne $dn.PublicUrl $dn.Url) }} + {{ $dn.PublicUrl }} + {{ else }} + {{ $dn.Url }} {{ end }} {{ $dn.Volumes }}