dragonfly/client/daemon/announcer/announcer.go

321 lines
8.7 KiB
Go

/*
* Copyright 2022 The Dragonfly 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.
*/
//go:generate mockgen -destination mocks/announcer_mock.go -source announcer.go -package mocks
package announcer
import (
"context"
"os"
"time"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/mem"
gopsutilnet "github.com/shirou/gopsutil/v3/net"
"github.com/shirou/gopsutil/v3/process"
managerv1 "d7y.io/api/pkg/apis/manager/v1"
schedulerv1 "d7y.io/api/pkg/apis/scheduler/v1"
"d7y.io/dragonfly/v2/client/config"
logger "d7y.io/dragonfly/v2/internal/dflog"
managerclient "d7y.io/dragonfly/v2/pkg/rpc/manager/client"
schedulerclient "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client"
"d7y.io/dragonfly/v2/pkg/types"
"d7y.io/dragonfly/v2/version"
)
// Announcer is the interface used for announce service.
type Announcer interface {
// Started announcer server.
Serve() error
// Stop announcer server.
Stop() error
}
// announcer provides announce function.
type announcer struct {
config *config.DaemonOption
hostID string
daemonPort int32
daemonDownloadPort int32
schedulerClient schedulerclient.V1
managerClient managerclient.V1
done chan struct{}
}
// Option is a functional option for configuring the announcer.
type Option func(s *announcer)
// WithManagerClient sets the grpc client of manager.
func WithManagerClient(client managerclient.V1) Option {
return func(a *announcer) {
a.managerClient = client
}
}
// New returns a new Announcer interface.
func New(cfg *config.DaemonOption, hostID string, daemonPort int32, daemonDownloadPort int32, schedulerClient schedulerclient.V1, options ...Option) Announcer {
a := &announcer{
config: cfg,
hostID: hostID,
daemonPort: daemonPort,
daemonDownloadPort: daemonDownloadPort,
schedulerClient: schedulerClient,
done: make(chan struct{}),
}
for _, opt := range options {
opt(a)
}
return a
}
// Started announcer server.
func (a *announcer) Serve() error {
if a.managerClient != nil {
logger.Info("announce seed peer to manager")
if err := a.announceToManager(); err != nil {
return err
}
}
logger.Info("announce peer to scheduler")
if err := a.announceToScheduler(); err != nil {
return err
}
return nil
}
// Stop announcer server.
func (a *announcer) Stop() error {
close(a.done)
return nil
}
// announceToScheduler announces peer information to scheduler.
func (a *announcer) announceToScheduler() error {
req, err := a.newAnnounceHostRequest()
if err != nil {
return err
}
if err := a.schedulerClient.AnnounceHost(context.Background(), req); err != nil {
logger.Errorf("announce for the first time failed: %s", err.Error())
}
// Announce to scheduler.
tick := time.NewTicker(a.config.Announcer.SchedulerInterval)
for {
select {
case <-tick.C:
req, err := a.newAnnounceHostRequest()
if err != nil {
logger.Error(err)
break
}
if err := a.schedulerClient.AnnounceHost(context.Background(), req); err != nil {
logger.Error(err)
break
}
case <-a.done:
return nil
}
}
}
// newAnnounceHostRequest returns announce host request.
func (a *announcer) newAnnounceHostRequest() (*schedulerv1.AnnounceHostRequest, error) {
hostType := types.HostTypeNormalName
if a.config.Scheduler.Manager.SeedPeer.Enable {
hostType = types.HostTypeSuperSeedName
}
pid := os.Getpid()
h, err := host.Info()
if err != nil {
return nil, err
}
proc, err := process.NewProcess(int32(pid))
if err != nil {
return nil, err
}
procCPUPercent, err := proc.CPUPercent()
if err != nil {
return nil, err
}
cpuPercent, err := cpu.Percent(0, false)
if err != nil {
return nil, err
}
cpuLogicalCount, err := cpu.Counts(true)
if err != nil {
return nil, err
}
cpuPhysicalCount, err := cpu.Counts(false)
if err != nil {
return nil, err
}
cpuTimes, err := cpu.Times(false)
if err != nil {
return nil, err
}
procMemoryPercent, err := proc.MemoryPercent()
if err != nil {
return nil, err
}
virtualMemory, err := mem.VirtualMemory()
if err != nil {
return nil, err
}
procTCPConnections, err := gopsutilnet.ConnectionsPid("tcp", int32(pid))
if err != nil {
return nil, err
}
var uploadTCPConnections []gopsutilnet.ConnectionStat
for _, procTCPConnection := range procTCPConnections {
if procTCPConnection.Laddr.Port == uint32(a.daemonDownloadPort) && procTCPConnection.Status == "ESTABLISHED" {
uploadTCPConnections = append(uploadTCPConnections, procTCPConnection)
}
}
tcpConnections, err := gopsutilnet.Connections("tcp")
if err != nil {
return nil, err
}
disk, err := disk.Usage(a.config.Storage.DataPath)
if err != nil {
return nil, err
}
return &schedulerv1.AnnounceHostRequest{
Id: a.hostID,
Type: hostType,
Hostname: a.config.Host.Hostname,
Ip: a.config.Host.AdvertiseIP.String(),
Port: a.daemonPort,
DownloadPort: a.daemonDownloadPort,
Os: h.OS,
Platform: h.Platform,
PlatformFamily: h.PlatformFamily,
PlatformVersion: h.PlatformVersion,
KernelVersion: h.KernelVersion,
Cpu: &schedulerv1.CPU{
LogicalCount: uint32(cpuLogicalCount),
PhysicalCount: uint32(cpuPhysicalCount),
Percent: cpuPercent[0],
ProcessPercent: procCPUPercent,
Times: &schedulerv1.CPUTimes{
User: cpuTimes[0].User,
System: cpuTimes[0].System,
Idle: cpuTimes[0].Idle,
Nice: cpuTimes[0].Nice,
Iowait: cpuTimes[0].Iowait,
Irq: cpuTimes[0].Irq,
Softirq: cpuTimes[0].Softirq,
Steal: cpuTimes[0].Steal,
Guest: cpuTimes[0].Guest,
GuestNice: cpuTimes[0].GuestNice,
},
},
Memory: &schedulerv1.Memory{
Total: virtualMemory.Total,
Available: virtualMemory.Available,
Used: virtualMemory.Used,
UsedPercent: virtualMemory.UsedPercent,
ProcessUsedPercent: float64(procMemoryPercent),
Free: virtualMemory.Free,
},
Network: &schedulerv1.Network{
TcpConnectionCount: uint32(len(tcpConnections)),
UploadTcpConnectionCount: uint32(len(uploadTCPConnections)),
Location: a.config.Host.Location,
Idc: a.config.Host.IDC,
},
Disk: &schedulerv1.Disk{
Total: disk.Total,
Free: disk.Free,
Used: disk.Used,
UsedPercent: disk.UsedPercent,
InodesTotal: disk.InodesTotal,
InodesUsed: disk.InodesUsed,
InodesFree: disk.InodesFree,
InodesUsedPercent: disk.InodesUsedPercent,
},
Build: &schedulerv1.Build{
GitVersion: version.GitVersion,
GitCommit: version.GitCommit,
GoVersion: version.GoVersion,
Platform: version.Platform,
},
}, nil
}
// announceSeedPeer announces peer information to manager.
func (a *announcer) announceToManager() error {
// Accounce seed peer information to manager.
if a.config.Scheduler.Manager.SeedPeer.Enable {
var objectStoragePort int32
if a.config.ObjectStorage.Enable {
objectStoragePort = int32(a.config.ObjectStorage.TCPListen.PortRange.Start)
}
if _, err := a.managerClient.UpdateSeedPeer(context.Background(), &managerv1.UpdateSeedPeerRequest{
SourceType: managerv1.SourceType_SEED_PEER_SOURCE,
Hostname: a.config.Host.Hostname,
Type: a.config.Scheduler.Manager.SeedPeer.Type,
Idc: a.config.Host.IDC,
Location: a.config.Host.Location,
Ip: a.config.Host.AdvertiseIP.String(),
Port: a.daemonPort,
DownloadPort: a.daemonDownloadPort,
ObjectStoragePort: objectStoragePort,
SeedPeerClusterId: uint64(a.config.Scheduler.Manager.SeedPeer.ClusterID),
}); err != nil {
return err
}
// Start keepalive to manager.
go a.managerClient.KeepAlive(a.config.Scheduler.Manager.SeedPeer.KeepAlive.Interval, &managerv1.KeepAliveRequest{
SourceType: managerv1.SourceType_SEED_PEER_SOURCE,
Hostname: a.config.Host.Hostname,
Ip: a.config.Host.AdvertiseIP.String(),
ClusterId: uint64(a.config.Scheduler.Manager.SeedPeer.ClusterID),
}, a.done)
}
return nil
}