github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/infradriver/ssh_infradriver.go (about)

     1  // Copyright © 2022 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package infradriver
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"github.com/containers/buildah/util"
    25  	"github.com/imdario/mergo"
    26  	"github.com/sealerio/sealer/common"
    27  	v1 "github.com/sealerio/sealer/types/api/v1"
    28  	v2 "github.com/sealerio/sealer/types/api/v2"
    29  	mapUtils "github.com/sealerio/sealer/utils/maps"
    30  	"github.com/sealerio/sealer/utils/shellcommand"
    31  	"github.com/sealerio/sealer/utils/ssh"
    32  	strUtil "github.com/sealerio/sealer/utils/strings"
    33  	"golang.org/x/sync/errgroup"
    34  	k8sv1 "k8s.io/api/core/v1"
    35  	k8snet "k8s.io/utils/net"
    36  )
    37  
    38  type SSHInfraDriver struct {
    39  	sshConfigs   map[string]ssh.Interface
    40  	hosts        []net.IP
    41  	hostTaint    map[string][]k8sv1.Taint
    42  	hostRolesMap map[string][]string
    43  	roleHostsMap map[string][]net.IP
    44  	hostLabels   map[string]map[string]string
    45  	hostEnvMap   map[string]map[string]string
    46  	clusterEnv   map[string]string
    47  	cluster      v2.Cluster
    48  }
    49  
    50  func convertTaints(taints []string) ([]k8sv1.Taint, error) {
    51  	var k8staints []k8sv1.Taint
    52  	for _, taint := range taints {
    53  		data, err := formatData(taint)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		k8staints = append(k8staints, data)
    58  	}
    59  	return k8staints, nil
    60  }
    61  
    62  // NewInfraDriver will create a new Infra driver, and if extraEnv specified, it will set env not exist in Cluster
    63  func NewInfraDriver(cluster *v2.Cluster) (InfraDriver, error) {
    64  	var err error
    65  	ret := &SSHInfraDriver{
    66  		cluster:      *cluster,
    67  		sshConfigs:   map[string]ssh.Interface{},
    68  		roleHostsMap: map[string][]net.IP{},
    69  		hostRolesMap: map[string][]string{},
    70  		// todo need to separate env into app render data and sys render data
    71  		hostEnvMap: map[string]map[string]string{},
    72  		hostLabels: map[string]map[string]string{},
    73  		hostTaint:  map[string][]k8sv1.Taint{},
    74  	}
    75  
    76  	// initialize hosts field
    77  	for _, host := range cluster.Spec.Hosts {
    78  		ret.hosts = append(ret.hosts, host.IPS...)
    79  	}
    80  
    81  	if len(ret.hosts) == 0 {
    82  		return nil, fmt.Errorf("no hosts specified")
    83  	}
    84  
    85  	if err = checkAllHostsSameFamily(ret.hosts); err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	if k8snet.IsIPv6String(ret.hosts[0].String()) {
    90  		hostIPFamilyEnv := fmt.Sprintf("%s=%s", common.EnvHostIPFamily, k8snet.IPv6)
    91  		if !util.StringInSlice(hostIPFamilyEnv, cluster.Spec.Env) {
    92  			cluster.Spec.Env = append(cluster.Spec.Env, hostIPFamilyEnv)
    93  		}
    94  	}
    95  
    96  	// initialize sshConfigs field
    97  	for i := range cluster.Spec.Hosts {
    98  		if err = mergo.Merge(&cluster.Spec.Hosts[i].SSH, &cluster.Spec.SSH); err != nil {
    99  			return nil, err
   100  		}
   101  		for _, ip := range cluster.Spec.Hosts[i].IPS {
   102  			ret.sshConfigs[ip.String()] = ssh.NewSSHClient(&cluster.Spec.Hosts[i].SSH, true)
   103  		}
   104  	}
   105  
   106  	// initialize roleHostsMap field
   107  	for _, host := range cluster.Spec.Hosts {
   108  		for _, role := range host.Roles {
   109  			ips, ok := ret.roleHostsMap[role]
   110  			if !ok {
   111  				ret.roleHostsMap[role] = host.IPS
   112  			} else {
   113  				ret.roleHostsMap[role] = append(ips, host.IPS...)
   114  			}
   115  		}
   116  		for _, ip := range host.IPS {
   117  			ret.hostRolesMap[ip.String()] = host.Roles
   118  		}
   119  	}
   120  
   121  	ret.clusterEnv = strUtil.ConvertStringSliceToMap(cluster.Spec.Env)
   122  
   123  	// initialize hostEnvMap and host labels field
   124  	// merge the host ENV and global env, the host env will overwrite cluster.Spec.Env
   125  	for _, host := range cluster.Spec.Hosts {
   126  		for _, ip := range host.IPS {
   127  			ret.hostEnvMap[ip.String()] = mapUtils.Merge(strUtil.ConvertStringSliceToMap(host.Env), ret.clusterEnv)
   128  			ret.hostLabels[ip.String()] = host.Labels
   129  		}
   130  	}
   131  
   132  	for _, host := range cluster.Spec.Hosts {
   133  		for _, ip := range host.IPS {
   134  			ret.hostTaint[ip.String()], err = convertTaints(host.Taints)
   135  			if err != nil {
   136  				return nil, err
   137  			}
   138  		}
   139  	}
   140  
   141  	return ret, err
   142  }
   143  
   144  func (d *SSHInfraDriver) GetHostTaints(host net.IP) []k8sv1.Taint {
   145  	return d.hostTaint[host.String()]
   146  }
   147  
   148  func (d *SSHInfraDriver) GetHostIPList() []net.IP {
   149  	return d.hosts
   150  }
   151  
   152  func (d *SSHInfraDriver) GetHostIPListByRole(role string) []net.IP {
   153  	return d.roleHostsMap[role]
   154  }
   155  
   156  func (d *SSHInfraDriver) GetRoleListByHostIP(ip string) []string {
   157  	return d.hostRolesMap[ip]
   158  }
   159  
   160  func (d *SSHInfraDriver) GetHostEnv(host net.IP) map[string]string {
   161  	// Set env for each host
   162  	hostEnv := d.hostEnvMap[host.String()]
   163  	if _, ok := hostEnv[common.EnvHostIP]; !ok {
   164  		hostEnv[common.EnvHostIP] = host.String()
   165  	}
   166  	return hostEnv
   167  }
   168  
   169  func (d *SSHInfraDriver) GetHostLabels(host net.IP) map[string]string {
   170  	return d.hostLabels[host.String()]
   171  }
   172  
   173  func (d *SSHInfraDriver) GetClusterEnv() map[string]string {
   174  	return d.clusterEnv
   175  }
   176  
   177  func (d *SSHInfraDriver) AddClusterEnv(envs []string) {
   178  	if d.clusterEnv == nil || envs == nil {
   179  		return
   180  	}
   181  	newEnv := strUtil.ConvertStringSliceToMap(envs)
   182  	for k, v := range newEnv {
   183  		d.clusterEnv[k] = v
   184  	}
   185  }
   186  
   187  func (d *SSHInfraDriver) GetClusterRegistry() v2.Registry {
   188  	return d.cluster.Spec.Registry
   189  }
   190  
   191  func (d *SSHInfraDriver) Copy(host net.IP, localFilePath, remoteFilePath string) error {
   192  	client := d.sshConfigs[host.String()]
   193  	if client == nil {
   194  		return fmt.Errorf("ip(%s) is not in cluster", host.String())
   195  	}
   196  	return client.Copy(host, localFilePath, remoteFilePath)
   197  }
   198  
   199  func (d *SSHInfraDriver) CopyR(host net.IP, remoteFilePath, localFilePath string) error {
   200  	client := d.sshConfigs[host.String()]
   201  	if client == nil {
   202  		return fmt.Errorf("ip(%s) is not in cluster", host.String())
   203  	}
   204  	//client.CopyR take remoteFilePath as src file
   205  	return client.CopyR(host, localFilePath, remoteFilePath)
   206  }
   207  
   208  func (d *SSHInfraDriver) CmdAsync(host net.IP, env map[string]string, cmd ...string) error {
   209  	client := d.sshConfigs[host.String()]
   210  	if client == nil {
   211  		return fmt.Errorf("ip(%s) is not in cluster", host.String())
   212  	}
   213  	return client.CmdAsync(host, env, cmd...)
   214  }
   215  
   216  func (d *SSHInfraDriver) Cmd(host net.IP, env map[string]string, cmd string) ([]byte, error) {
   217  	client := d.sshConfigs[host.String()]
   218  	if client == nil {
   219  		return nil, fmt.Errorf("ip(%s) is not in cluster", host.String())
   220  	}
   221  	return client.Cmd(host, env, cmd)
   222  }
   223  
   224  func (d *SSHInfraDriver) CmdToString(host net.IP, env map[string]string, cmd, spilt string) (string, error) {
   225  	client := d.sshConfigs[host.String()]
   226  	if client == nil {
   227  		return "", fmt.Errorf("ip(%s) is not in cluster", host.String())
   228  	}
   229  	return client.CmdToString(host, env, cmd, spilt)
   230  }
   231  
   232  func (d *SSHInfraDriver) IsFileExist(host net.IP, remoteFilePath string) (bool, error) {
   233  	client := d.sshConfigs[host.String()]
   234  	if client == nil {
   235  		return false, fmt.Errorf("ip(%s) is not in cluster", host.String())
   236  	}
   237  	return client.IsFileExist(host, remoteFilePath)
   238  }
   239  
   240  func (d *SSHInfraDriver) IsDirExist(host net.IP, remoteDirPath string) (bool, error) {
   241  	client := d.sshConfigs[host.String()]
   242  	if client == nil {
   243  		return false, fmt.Errorf("ip(%s) is not in cluster", host.String())
   244  	}
   245  	return client.RemoteDirExist(host, remoteDirPath)
   246  }
   247  
   248  func (d *SSHInfraDriver) GetPlatform(host net.IP) (v1.Platform, error) {
   249  	client := d.sshConfigs[host.String()]
   250  	if client == nil {
   251  		return v1.Platform{}, fmt.Errorf("ip(%s) is not in cluster", host.String())
   252  	}
   253  	return client.GetPlatform(host)
   254  }
   255  
   256  func (d *SSHInfraDriver) Ping(host net.IP) error {
   257  	client := d.sshConfigs[host.String()]
   258  	if client == nil {
   259  		return fmt.Errorf("ip(%s) is not in cluster", host.String())
   260  	}
   261  	return client.Ping(host)
   262  }
   263  
   264  func (d *SSHInfraDriver) SetHostName(host net.IP, hostName string) error {
   265  	setHostNameCmd := fmt.Sprintf("hostnamectl set-hostname %s", hostName)
   266  	return d.CmdAsync(host, nil, setHostNameCmd)
   267  }
   268  
   269  func (d *SSHInfraDriver) SetClusterHostAliases(hosts []net.IP) error {
   270  	for _, host := range hosts {
   271  		for _, hostAliases := range d.cluster.Spec.HostAliases {
   272  			hostname := strings.Join(hostAliases.Hostnames, " ")
   273  			err := d.CmdAsync(host, nil, shellcommand.CommandSetHostAlias(hostname, hostAliases.IP))
   274  			if err != nil {
   275  				return err
   276  			}
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  func (d *SSHInfraDriver) DeleteClusterHostAliases(hosts []net.IP) error {
   283  	for _, host := range hosts {
   284  		err := d.CmdAsync(host, nil, shellcommand.CommandUnSetHostAlias())
   285  		if err != nil {
   286  			return err
   287  		}
   288  	}
   289  	return nil
   290  }
   291  
   292  func (d *SSHInfraDriver) GetClusterName() string {
   293  	return d.cluster.Name
   294  }
   295  
   296  func (d *SSHInfraDriver) GetClusterImageName() string {
   297  	return d.cluster.Spec.Image
   298  }
   299  
   300  func (d *SSHInfraDriver) GetClusterLaunchCmds() []string {
   301  	return d.cluster.Spec.CMD
   302  }
   303  
   304  func (d *SSHInfraDriver) GetClusterLaunchApps() []string {
   305  	return d.cluster.Spec.APPNames
   306  }
   307  
   308  func (d *SSHInfraDriver) GetHostName(hostIP net.IP) (string, error) {
   309  	hostName, err := d.CmdToString(hostIP, nil, "uname -n", "")
   310  	if err != nil {
   311  		return "", err
   312  	}
   313  	if hostName == "" {
   314  		return "", fmt.Errorf("faild to get remote hostname of host(%s)", hostIP.String())
   315  	}
   316  
   317  	return strings.ToLower(hostName), nil
   318  }
   319  
   320  func (d *SSHInfraDriver) GetHostsPlatform(hosts []net.IP) (map[v1.Platform][]net.IP, error) {
   321  	hostsPlatformMap := make(map[v1.Platform][]net.IP)
   322  
   323  	for _, ip := range hosts {
   324  		plat, err := d.GetPlatform(ip)
   325  		if err != nil {
   326  			return nil, err
   327  		}
   328  
   329  		_, ok := hostsPlatformMap[plat]
   330  		if !ok {
   331  			hostsPlatformMap[plat] = []net.IP{ip}
   332  		} else {
   333  			hostsPlatformMap[plat] = append(hostsPlatformMap[plat], ip)
   334  		}
   335  	}
   336  
   337  	return hostsPlatformMap, nil
   338  }
   339  
   340  func (d *SSHInfraDriver) GetClusterRootfsPath() string {
   341  	dataRoot := d.cluster.Spec.DataRoot
   342  	if dataRoot == "" {
   343  		dataRoot = common.DefaultSealerDataDir
   344  	}
   345  
   346  	return filepath.Join(dataRoot, d.cluster.Name, "rootfs")
   347  }
   348  
   349  func (d *SSHInfraDriver) GetClusterBasePath() string {
   350  	dataRoot := d.cluster.Spec.DataRoot
   351  	if dataRoot == "" {
   352  		dataRoot = common.DefaultSealerDataDir
   353  	}
   354  
   355  	return filepath.Join(dataRoot, d.cluster.Name)
   356  }
   357  
   358  func (d *SSHInfraDriver) Execute(hosts []net.IP, f func(host net.IP) error) error {
   359  	eg, _ := errgroup.WithContext(context.Background())
   360  	for _, ip := range hosts {
   361  		host := ip
   362  		eg.Go(func() error {
   363  			err := f(host)
   364  			if err != nil {
   365  				return fmt.Errorf("on host [%s]: %v", host.String(), err)
   366  			}
   367  			return nil
   368  		})
   369  	}
   370  
   371  	if err := eg.Wait(); err != nil {
   372  		return err
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  func checkAllHostsSameFamily(nodeList []net.IP) error {
   379  	var netFamily bool
   380  	for i, ip := range nodeList {
   381  		if i == 0 {
   382  			netFamily = k8snet.IsIPv4(ip)
   383  		}
   384  
   385  		if netFamily != k8snet.IsIPv4(ip) {
   386  			return fmt.Errorf("all hosts must be in same ip family, but the node list given are mixed with ipv4 and ipv6: %v", nodeList)
   387  		}
   388  	}
   389  	return nil
   390  }