-
Notifications
You must be signed in to change notification settings - Fork 360
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pkg: add an underlying package hostpool to manage general host operation
Signed-off-by: Allen Sun <[email protected]>
- Loading branch information
1 parent
cd61b99
commit dd591b0
Showing
16 changed files
with
1,453 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright © 2022 Alibaba Group Holding Ltd. | ||
// | ||
// 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 hostpool | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"strconv" | ||
|
||
goscp "github.com/bramvdbogaerde/go-scp" | ||
"github.com/pkg/sftp" | ||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
// Host contains both static and dynamic information of a host machine. | ||
// Static part: the host config | ||
// dynamic part, including ssh client and sftp client. | ||
type Host struct { | ||
config HostConfig | ||
|
||
// sshClient is used to create ssh.Session. | ||
// TODO: remove this and just make ssh.Session remain. | ||
sshClient *ssh.Client | ||
// sshSession is created by ssh.Client and used for command execution on specified host. | ||
sshSession *ssh.Session | ||
// sftpClient is used to file remote operation on specified host except scp operation. | ||
sftpClient *sftp.Client | ||
// scpClient is used to scp files between sealer node and all nodes. | ||
scpClient *goscp.Client | ||
|
||
// isLocal identifies that whether the initialized host is the sealer binary located node. | ||
isLocal bool | ||
} | ||
|
||
// HostConfig is the host config, including IP, port, login credentials and so on. | ||
type HostConfig struct { | ||
// IP is the IP address of host. | ||
// It supports both IPv4 and IPv6. | ||
IP net.IP | ||
|
||
// Port is the port config used by ssh to connect host | ||
// The connecting operation will use port 22 if port is not set. | ||
Port int | ||
|
||
// Usually User will be root. If it is set a non-root user, | ||
// then this non-root must has a sudo permission. | ||
User string | ||
Password string | ||
|
||
// Encrypted means the password is encrypted. | ||
// Password above should be decrypted first before being called. | ||
Encrypted bool | ||
|
||
// TODO: add PkFile support | ||
// PkFile string | ||
// PkPassword string | ||
} | ||
|
||
// Initialize setups ssh and sftp clients. | ||
func (host *Host) Initialize() error { | ||
config := &ssh.ClientConfig{ | ||
User: host.config.User, | ||
Auth: []ssh.AuthMethod{ | ||
ssh.Password(host.config.Password), | ||
}, | ||
HostKeyCallback: nil, | ||
} | ||
|
||
hostAddr := host.config.IP.String() | ||
port := strconv.Itoa(host.config.Port) | ||
|
||
// sshClient | ||
sshClient, err := ssh.Dial("tcp", net.JoinHostPort(hostAddr, port), config) | ||
if err != nil { | ||
return fmt.Errorf("failed to create ssh client for host(%s): %v", hostAddr, err) | ||
} | ||
host.sshClient = sshClient | ||
|
||
// sshSession | ||
sshSession, err := sshClient.NewSession() | ||
if err != nil { | ||
return fmt.Errorf("failed to create ssh session for host(%s): %v", hostAddr, err) | ||
} | ||
host.sshSession = sshSession | ||
|
||
// sftpClient | ||
sftpClient, err := sftp.NewClient(sshClient, nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to create sftp client for host(%s): %v", hostAddr, err) | ||
} | ||
host.sftpClient = sftpClient | ||
|
||
// scpClient | ||
scpClient, err := goscp.NewClientBySSH(sshClient) | ||
if err != nil { | ||
return fmt.Errorf("failed to create scp client for host(%s): %v", hostAddr, err) | ||
} | ||
host.scpClient = &scpClient | ||
|
||
// TODO: set isLocal | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright © 2022 Alibaba Group Holding Ltd. | ||
// | ||
// 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 hostpool | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// HostPool is a host resource pool of sealer's cluster, including masters and nodes. | ||
// While SEALER DEPLOYING NODE has no restrict relationship with masters nor nodes: | ||
// 1. sealer deploying node could be a node which is no master nor node; | ||
// 2. sealer deploying node could also be one of masters and nodes. | ||
// Then deploying node is not included in HostPool. | ||
type HostPool struct { | ||
// host is a map: | ||
// key has a type of string which is from net.Ip.String() | ||
hosts map[string]*Host | ||
} | ||
|
||
// New initializes a brand new HostPool instance. | ||
func New(hostConfigs []*HostConfig) (*HostPool, error) { | ||
if len(hostConfigs) == 0 { | ||
return nil, fmt.Errorf("input HostConfigs cannot be empty") | ||
} | ||
var hostPool HostPool | ||
for _, hostConfig := range hostConfigs { | ||
if _, OK := hostPool.hosts[hostConfig.IP.String()]; OK { | ||
return nil, fmt.Errorf("there must not be duplicated host IP(%s) in cluster hosts", hostConfig.IP.String()) | ||
} | ||
hostPool.hosts[hostConfig.IP.String()] = &Host{ | ||
config: HostConfig{ | ||
IP: hostConfig.IP, | ||
Port: hostConfig.Port, | ||
User: hostConfig.User, | ||
Password: hostConfig.Password, | ||
Encrypted: hostConfig.Encrypted, | ||
}, | ||
} | ||
} | ||
return &hostPool, nil | ||
} | ||
|
||
// Initialize helps HostPool to setup all attributes for each host, | ||
// like scpClient, sshClient and so on. | ||
func (hp *HostPool) Initialize() error { | ||
for _, host := range hp.hosts { | ||
if err := host.Initialize(); err != nil { | ||
return fmt.Errorf("failed to initialize host in HostPool: %v", err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// GetHost gets the detailed host connection instance via IP string as a key. | ||
func (hp *HostPool) GetHost(ipStr string) (*Host, error) { | ||
if host, exist := hp.hosts[ipStr]; exist { | ||
return host, nil | ||
} | ||
return nil, fmt.Errorf("cannot get host connection in HostPool by key(%s)", ipStr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright © 2022 Alibaba Group Holding Ltd. | ||
// | ||
// 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 hostpool | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// CopyFile copies the contents of localFilePath to remote destination path. | ||
// Both localFilePath and remotePath must be an absolute path. | ||
// | ||
// It must be executed in deploying node and towards the host instance. | ||
func (host *Host) CopyToRemote(localFilePath string, remotePath string, permissions string) error { | ||
if host.isLocal { | ||
// TODO: add local file copy. | ||
return fmt.Errorf("local file copy is not implemented") | ||
} | ||
|
||
f, err := os.Open(filepath.Clean(localFilePath)) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
return host.scpClient.CopyFromFile(context.Background(), *f, remotePath, permissions) | ||
} | ||
|
||
// CopyFile copies the contents of remotePath to local destination path. | ||
// Both localFilePath and remotePath must be an absolute path. | ||
// | ||
// It must be executed in deploying node and towards the host instance. | ||
func (host *Host) CopyFromRemote(localFilePath string, remotePath string) error { | ||
if host.isLocal { | ||
// TODO: add local file copy. | ||
return fmt.Errorf("local file copy is not implemented") | ||
} | ||
|
||
f, err := os.Open(filepath.Clean(localFilePath)) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
return host.scpClient.CopyFromRemote(context.Background(), f, remotePath) | ||
} | ||
|
||
// CopyToRemoteDir copies the contents of local directory to remote destination directory. | ||
// Both localFilePath and remotePath must be an absolute path. | ||
// | ||
// It must be executed in deploying node and towards the host instance. | ||
func (host *Host) CopyToRemoteDir(localDir string, remoteDir string) error { | ||
if host.isLocal { | ||
// TODO: add local file copy. | ||
return fmt.Errorf("local file copy is not implemented") | ||
} | ||
|
||
// get the localDir Directory name | ||
fInfo, err := os.Lstat(localDir) | ||
if err != nil { | ||
return err | ||
} | ||
if !fInfo.IsDir() { | ||
return fmt.Errorf("input localDir(%s) is not a directory when copying directory content", localDir) | ||
} | ||
dirName := fInfo.Name() | ||
|
||
err = filepath.Walk(localDir, func(path string, info fs.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
// Since localDir is an absolute path, then every passed path has a prefix of localDir, | ||
// then the relative path is the input path trims localDir. | ||
fileRelativePath := strings.TrimPrefix(path, localDir) | ||
remotePath := filepath.Join(remoteDir, dirName, fileRelativePath) | ||
|
||
return host.CopyToRemote(path, remotePath, info.Mode().String()) | ||
}) | ||
|
||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright © 2022 Alibaba Group Holding Ltd. | ||
// | ||
// 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 hostpool | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"os/exec" | ||
) | ||
|
||
// Output runs cmd on the remote host and returns its standard output. | ||
// It must be executed in deploying node and towards the host instance. | ||
func (host *Host) Output(cmd string) ([]byte, error) { | ||
if host.isLocal { | ||
return exec.Command(cmd).Output() | ||
} | ||
return host.sshSession.Output(cmd) | ||
} | ||
|
||
// CombinedOutput wraps the sshSession.CombinedOutput and does the same in both input and output. | ||
// It must be executed in deploying node and towards the host instance. | ||
func (host *Host) CombinedOutput(cmd string) ([]byte, error) { | ||
if host.isLocal { | ||
return exec.Command(cmd).CombinedOutput() | ||
} | ||
return host.sshSession.CombinedOutput(cmd) | ||
} | ||
|
||
// RunAndStderr runs a specified command and output stderr content. | ||
// If command returns a nil, then no matter if there is content in session's stderr, just ignore stderr; | ||
// If command return a non-nil, construct and return a new error with stderr content | ||
// which may contains the exact error message. | ||
// | ||
// TODO: there is a potential issue that if much content is in stdout or stderr, and | ||
// it may eventually cause the remote command to block. | ||
// | ||
// It must be executed in deploying node and towards the host instance. | ||
func (host *Host) RunAndStderr(cmd string) ([]byte, error) { | ||
var stdout, stderr bytes.Buffer | ||
if host.isLocal { | ||
localCmd := exec.Command(cmd) | ||
localCmd.Stdout = &stdout | ||
localCmd.Stderr = &stderr | ||
if err := localCmd.Run(); err != nil { | ||
return nil, fmt.Errorf("failed to exec cmd(%s) on host(%s): %s", cmd, host.config.IP, stderr.String()) | ||
} | ||
return stdout.Bytes(), nil | ||
} | ||
|
||
host.sshSession.Stdout = &stdout | ||
host.sshSession.Stderr = &stderr | ||
if err := host.sshSession.Run(cmd); err != nil { | ||
return nil, fmt.Errorf("failed to exec cmd(%s) on host(%s): %s", cmd, host.config.IP, stderr.String()) | ||
} | ||
|
||
return stdout.Bytes(), nil | ||
} | ||
|
||
// TODO: Do we need asynchronously output stdout and stderr? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright © 2022 Alibaba Group Holding Ltd. | ||
// | ||
// 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 hostpool |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.