fixed cluster operator auto to create kuberentes (#2139)

* fixed cluster operator to create kuberentes auto

Signed-off-by: fanux <fanux@sealos.io>

* add don't download the sealos binfile if already exist

* complete test for create kubernetes cluster

Signed-off-by: fanux <fanux@sealos.io>
This commit is contained in:
中弈
2022-11-23 17:57:29 +08:00
committed by GitHub
parent eff2fbfaa1
commit 46b4e8664d
11 changed files with 360 additions and 366 deletions
+5
View File
@@ -76,6 +76,11 @@ make manifests
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
## Cluster operator specify the sealos version
Using the annotations: "sealos.io/sealos/version"
Example: "sealos.io/sealos/version=4.1.3"
## License
Copyright 2022.
+34 -18
View File
@@ -21,24 +21,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// ClusterSpec defines the desired state of Cluster
type ClusterSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Infra string `json:"infra,omitempty"`
Images []string `json:"images,omitempty"`
SSH v1beta1.SSH `json:"ssh,omitempty"`
}
// ClusterStatus defines the observed state of Cluster
type ClusterStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Status string `json:"status,omitempty"`
}
type Status int
const (
@@ -64,6 +46,40 @@ func (s Status) String() string {
}
}
// ClusterSpec defines the desired state of InfraMetadata
type ClusterSpec struct {
// desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Cluster. Edit Cluster_types.go to remove/update
Infra string `json:"infra,omitempty"`
Image v1beta1.ImageList `json:"image,omitempty"`
SSH v1beta1.SSH `json:"ssh,omitempty"`
Hosts []v1beta1.Host `json:"hosts,omitempty"`
// Why env not using map[string]string
// Because some argument is list, like: CertSANS=127.0.0.1 CertSANS=localhost, if ENV is map, will merge those two values
// but user want to InfraMetadata a list, using array we can convert it to {CertSANS:[127.0.0.1, localhost]}
Env []string `json:"env,omitempty"`
// Entrypoint array. Not executed within a shell.
// The docker image's ENTRYPOINT is used if this is not provided.
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
// of whether the variable exists or not. Cannot be updated.
// More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
// +optional
Command []string `json:"command,omitempty"`
}
// ClusterStatus defines the observed state of Cluster
type ClusterStatus struct {
Phase v1beta1.ClusterPhase `json:"phase,omitempty"`
Mounts []v1beta1.MountImage `json:"mounts,omitempty"`
Conditions []v1beta1.ClusterCondition `json:"conditions,omitempty" `
// Important: Run "make" to regenerate code after modifying this file
Status string `json:"status,omitempty"`
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
@@ -22,6 +22,7 @@ limitations under the License.
package v1
import (
"github.com/labring/sealos/pkg/types/v1beta1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -30,8 +31,8 @@ func (in *Cluster) DeepCopyInto(out *Cluster) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.
@@ -87,6 +88,29 @@ func (in *ClusterList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
*out = *in
if in.Image != nil {
in, out := &in.Image, &out.Image
*out = make(v1beta1.ImageList, len(*in))
copy(*out, *in)
}
out.SSH = in.SSH
if in.Hosts != nil {
in, out := &in.Hosts, &out.Hosts
*out = make([]v1beta1.Host, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Command != nil {
in, out := &in.Command, &out.Command
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec.
@@ -102,6 +126,20 @@ func (in *ClusterSpec) DeepCopy() *ClusterSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
*out = *in
if in.Mounts != nil {
in, out := &in.Mounts, &out.Mounts
*out = make([]v1beta1.MountImage, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1beta1.ClusterCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus.
-210
View File
@@ -1,210 +0,0 @@
package applier
import (
"fmt"
"log"
"strconv"
cv1 "github.com/labring/sealos/controllers/cluster/api/v1"
v1 "github.com/labring/sealos/controllers/infra/api/v1"
"github.com/labring/sealos/controllers/infra/common"
"github.com/labring/sealos/pkg/ssh"
"github.com/labring/sealos/pkg/types/v1beta1"
)
type Applier struct{}
func NewApplier() Reconcile {
return &Applier{}
}
type Reconcile interface {
ReconcileCluster(infra *v1.Infra, hosts []v1.Hosts, cluster *cv1.Cluster) error
}
type IP struct {
PrivateIP string
PublicIP string
}
func (a *Applier) ReconcileCluster(infra *v1.Infra, hosts []v1.Hosts, cluster *cv1.Cluster) error {
//todo 在job中去做,能够实现fmt打印导出到前端
master := make([]IP, 0)
node := make([]IP, 0)
images := getImages(cluster)
for i := range infra.Spec.Hosts {
if i >= len(hosts) || infra.Spec.Hosts[i].Count != hosts[i].Count {
return fmt.Errorf("there is no expected number of instances")
}
if getRoleMaster(hosts[i].Roles) {
for _, j := range hosts[i].Metadata {
master = append(master, IP{getPrivateIP(j.IP), getPublicIP(j.IP)})
}
} else {
for _, j := range hosts[i].Metadata {
node = append(node, IP{getPrivateIP(j.IP), getPublicIP(j.IP)})
}
}
}
if len(master) == 0 {
return fmt.Errorf("zero master")
}
ssh, err := ssh.NewSSHByCluster(&v1beta1.Cluster{Spec: v1beta1.ClusterSpec{SSH: cluster.Spec.SSH}}, true)
if err != nil {
return fmt.Errorf("ssh create fail %v", err)
}
passwd := cluster.Spec.SSH.Passwd
if len(node) == 0 {
if len(master) == 1 {
// 1 master and zero node -> single
if err = makeClusterSingle(ssh, master[0].PublicIP, images); err != nil {
return fmt.Errorf("make cluster single error :%v", err)
}
return nil
}
return fmt.Errorf("zero node but more than 1 master")
}
// >= 1 master and >= 1 node
sealos := master[0].PublicIP
for i := 0; i < len(master); i++ {
ip := master[i].PublicIP
if err = makeClusterMaster(ssh, ip, passwd, "master"+strconv.Itoa(i)); err != nil {
return err
}
}
for i := 0; i < len(node); i++ {
ip := node[i].PublicIP
if err = makeClusterNode(ssh, ip, passwd, "node"+strconv.Itoa(i)); err != nil {
return err
}
}
ipMasters, ipNodes := getIPs(master), getIPs(node)
//sealos run on master
cmdSealosRun := fmt.Sprintf("sudo sealos run %s --masters %s --nodes %s --passwd %s", images, ipMasters, ipNodes, passwd)
log.Println(cmdSealosRun)
if err = ssh.CmdAsync(sealos, cmdSealosRun); err != nil {
return fmt.Errorf("execute ssh commend %s,fail:%v", cmdSealosRun, err)
}
return nil
}
func makeClusterSingle(ssh ssh.Interface, ip, images string) error {
if err := downLoadSealos(ssh, ip); err != nil {
return err
}
//sealos run on single
cmdSealosRun := fmt.Sprintf("sudo sealos run %s --single", images)
if err := ssh.CmdAsync(ip, cmdSealosRun); err != nil {
return fmt.Errorf("execute commend: %s,error: %v", cmdSealosRun, err)
}
return nil
}
// makeClusterNode configure ssh and root password
func makeClusterNode(ssh ssh.Interface, ip, passwd, role string) error {
if err := setRootPasswd(ssh, ip, passwd); err != nil {
return err
}
if err := setHostNameRole(ssh, ip, role); err != nil {
return err
}
if err := modifySSHConfig(ssh, ip); err != nil {
return err
}
return nil
}
func makeClusterMaster(ssh ssh.Interface, ip, passwd, role string) error {
if err := makeClusterNode(ssh, ip, passwd, role); err != nil {
return err
}
if err := downLoadSealos(ssh, ip); err != nil {
return err
}
return nil
}
func downLoadSealos(ssh ssh.Interface, ip string) error {
cmd := "sudo wget -c https://github.com/labring/sealos/releases/download/v4.1.3/sealos_4.1.3_linux_amd64.tar.gz && sudo tar zxvf sealos_4.1.3_linux_amd64.tar.gz sealos && sudo rm -rf sealos_4.1.3_linux_amd64.tar.gz && sudo chmod +x sealos && sudo mv sealos /usr/bin"
if err := ssh.CmdAsync(ip, cmd); err != nil {
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
}
return nil
}
func modifySSHConfig(ssh ssh.Interface, ip string) error {
cmd := "sudo sed -i '63c PasswordAuthentication yes' /etc/ssh/sshd_config && sudo sed -i 's/# StrictHostKeyChecking ask/StrictHostKeyChecking no/' /etc/ssh/ssh_config && sudo systemctl restart sshd"
if err := ssh.CmdAsync(ip, cmd); err != nil {
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
}
return nil
}
func setRootPasswd(ssh ssh.Interface, ip string, passwd string) error {
cmd := fmt.Sprintf("sudo echo -e %s\"\\n\"%s | sudo passwd root", passwd, passwd)
if err := ssh.CmdAsync(ip, cmd); err != nil {
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
}
return nil
}
func setHostNameRole(ssh ssh.Interface, ip string, role string) error {
cmd := fmt.Sprintf("sudo hostnamectl set-hostname %s", role)
if err := ssh.CmdAsync(ip, cmd); err != nil {
return fmt.Errorf("execute commend: %s,error: %v", cmd, err)
}
return nil
}
func getImages(cluster *cv1.Cluster) string {
images := ""
for _, image := range cluster.Spec.Images {
images += image + " "
}
return images
}
func getIPs(ips []IP) string {
ip := ""
for i := 0; i < len(ips); i++ {
if i == len(ips)-1 {
ip = ip + ips[i].PrivateIP
break
}
ip = ip + ips[i].PrivateIP + ","
}
return ip
}
func getRoleMaster(Roles []string) bool {
for _, j := range Roles {
if j == "master" {
return true
}
}
return false
}
func getPrivateIP(ips []v1.IPAddress) string {
for _, ip := range ips {
if ip.IPType == common.IPTypePrivate {
return ip.IPValue
}
}
return ""
}
func getPublicIP(ips []v1.IPAddress) string {
for _, ip := range ips {
if ip.IPType == common.IPTypePublic {
return ip.IPValue
}
}
return ""
}
@@ -1,56 +0,0 @@
package applier
import (
"testing"
cv1 "github.com/labring/sealos/controllers/cluster/api/v1"
v1 "github.com/labring/sealos/controllers/infra/api/v1"
"github.com/labring/sealos/pkg/types/v1beta1"
)
func TestApplier_ReconcileCluster(t *testing.T) {
type args struct {
infra *v1.Infra
hosts []v1.Hosts
cluster *cv1.Cluster
}
hosts := []v1.Hosts{
{
Roles: []string{"master"},
Metadata: []v1.Metadata{},
},
}
cluster := &cv1.Cluster{
Spec: cv1.ClusterSpec{
Images: []string{"labring/kubernetes:v1.24.0", "labring/calico:v3.22.1"},
},
}
tests := []struct {
name string
args args
wantErr bool
}{
{
"test for apply cluster",
args{
infra: &v1.Infra{Spec: v1.InfraSpec{SSH: v1beta1.SSH{
User: "ec2-user",
Passwd: "123456",
Pk: "/root/hurz_key.pem",
Port: uint16(22),
}}},
hosts: hosts,
cluster: cluster,
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Applier{}
if err := a.ReconcileCluster(tt.args.infra, tt.args.hosts, tt.args.cluster); (err != nil) != tt.wantErr {
t.Errorf("ReconcileCluster() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
@@ -33,15 +33,55 @@ spec:
metadata:
type: object
spec:
description: ClusterSpec defines the desired state of Cluster
description: ClusterSpec defines the desired state of InfraMetadata
properties:
images:
command:
description: 'Entrypoint array. Not executed within a shell. The docker
image''s ENTRYPOINT is used if this is not provided. Variable references
$(VAR_NAME) are expanded using the container''s environment. If
a variable cannot be resolved, the reference in the input string
will be unchanged. Double $$ are reduced to a single $, which allows
for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce
the string literal "$(VAR_NAME)". Escaped references will never
be expanded, regardless of whether the variable exists or not. Cannot
be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'
items:
type: string
type: array
env:
description: 'Why env not using map[string]string Because some argument
is list, like: CertSANS=127.0.0.1 CertSANS=localhost, if ENV is
map, will merge those two values but user want to InfraMetadata
a list, using array we can convert it to {CertSANS:[127.0.0.1, localhost]}'
items:
type: string
type: array
hosts:
items:
properties:
env:
description: overwrite env
items:
type: string
type: array
ips:
items:
type: string
type: array
roles:
items:
type: string
type: array
type: object
type: array
image:
items:
type: string
type: array
infra:
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
description: 'desired state of cluster Important: Run "make" to regenerate
code after modifying this file Foo is an example field of Cluster.
Edit Cluster_types.go to remove/update'
type: string
ssh:
properties:
@@ -49,12 +89,12 @@ spec:
type: string
pk:
type: string
pkData:
type: string
pkName:
type: string
pkPasswd:
type: string
pkdata:
type: string
pkname:
type: string
port:
type: integer
user:
@@ -64,9 +104,65 @@ spec:
status:
description: ClusterStatus defines the observed state of Cluster
properties:
conditions:
items:
description: ClusterCondition describes the state of a cluster at
a certain point.
properties:
lastHeartbeatTime:
format: date-time
type: string
message:
type: string
reason:
type: string
status:
type: string
type:
type: string
required:
- status
- type
type: object
type: array
mounts:
items:
properties:
cmd:
items:
type: string
type: array
entrypoint:
items:
type: string
type: array
env:
additionalProperties:
type: string
type: object
imageName:
type: string
labels:
additionalProperties:
type: string
type: object
mountPoint:
type: string
name:
type: string
type:
type: string
required:
- imageName
- mountPoint
- name
- type
type: object
type: array
phase:
type: string
status:
description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
of cluster Important: Run "make" to regenerate code after modifying
description: 'Important: Run "make" to regenerate code after modifying
this file'
type: string
type: object
+24 -30
View File
@@ -1,36 +1,30 @@
apiVersion: infra.sealos.io/v1
kind: Infra
metadata:
name: infra-apply-test
spec:
ssh:
user: ec2-user
passwd: "123456"
pk: /root/hurz_key.pem
pkname: hurz_key
port: 22
hosts:
- roles: [master] # required
count: 1 # Required
flavor: "t2.large"
image: "ami-05248307900d52e3a"
- roles: [ node ] # required
count: 1 # Required
flavor: "t2.medium"
image: "ami-05248307900d52e3a"
---
#apiVersion: infra.sealos.io/v1
#kind: Infra
#metadata:
# name: infra-apply-test
#spec:
# ssh:
# user: ec2-user
# passwd: "123456"
# pk: /root/hurz_key.pem
# pkname: hurz_key
# port: 22
# hosts:
# - roles: [master] # required
# count: 1 # Required
# flavor: "t2.large"
# image: "ami-05248307900d52e3a"
# - roles: [ node ] # required
# count: 1 # Required
# flavor: "t2.medium"
# image: "ami-05248307900d52e3a"
#---
apiVersion: cluster.sealos.io/v1
kind: Cluster
metadata:
name: my-cluster
spec:
ssh:
user: ec2-user
passwd: "123456"
pk: /root/hurz_key.pem
pkname: hurz_key
port: 22
infra: infra-apply-test
images:
- labring/kubernetes:v1.24.0
- labring/calico:v3.22.1
image:
- labring/kubernetes:v1.24.0
- labring/calico:v3.22.1
@@ -1,5 +1,5 @@
/*
Copyright 2022.
Copyright 2022 labring/sealos.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,26 +19,45 @@ package controllers
import (
"context"
"fmt"
"time"
"github.com/labring/sealos/controllers/cluster/applier"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
infracommon "github.com/labring/sealos/controllers/infra/common"
"github.com/labring/sealos/pkg/ssh"
"sigs.k8s.io/yaml"
"github.com/labring/sealos/pkg/types/v1beta1"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/client-go/tools/record"
v1 "github.com/labring/sealos/controllers/cluster/api/v1"
infrav1 "github.com/labring/sealos/controllers/infra/api/v1"
"github.com/labring/sealos/controllers/infra/drivers"
"github.com/labring/sealos/pkg/utils/logger"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
v1 "github.com/labring/sealos/controllers/cluster/api/v1"
const (
defaultUser = "root"
defaultSealosVersion = "4.1.4-rc1"
)
const (
applyClusterfileCmd = "sealos apply -f /root/Clusterfile"
downloadSealosCmd = `sealos version || wget https://github.com/labring/sealos/releases/download/v%s/sealos_%s_linux_amd64.tar.gz && tar -zxvf sealos_%s_linux_amd64.tar.gz sealos && chmod +x sealos && mv sealos /usr/bin`
)
// ClusterReconciler reconciles a Cluster object
type ClusterReconciler struct {
client.Client
driver drivers.Driver
applier applier.Reconcile
Scheme *runtime.Scheme
driver drivers.Driver
Scheme *runtime.Scheme
recorder record.EventRecorder
}
//+kubebuilder:rbac:groups=cluster.sealos.io,resources=clusters,verbs=get;list;watch;create;update;patch;delete
@@ -56,41 +75,131 @@ type ClusterReconciler struct {
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
cluster := &v1.Cluster{}
if err := r.Get(ctx, req.NamespacedName, cluster); err != nil {
logger.Debug("ignore not found cluster error: %v", err)
r.recorder.Event(cluster, core.EventTypeWarning, "GetCluster", err.Error())
return ctrl.Result{}, client.IgnoreNotFound(err)
}
infra := &infrav1.Infra{}
infra.Name = cluster.Spec.Infra
infra.Namespace = cluster.Namespace
key := client.ObjectKey{Namespace: infra.Namespace, Name: infra.Name}
if err := r.Get(ctx, key, infra); err != nil {
logger.Debug("ignore not found cluster error: %v", err)
r.recorder.Event(cluster, core.EventTypeWarning, "GetInfra", err.Error())
return ctrl.Result{}, client.IgnoreNotFound(err)
}
hosts, err := r.driver.GetInstances(infra)
clusterfile, err := generateClusterfile(infra, cluster)
if err != nil {
logger.Error("get instances error: %v", err)
return ctrl.Result{}, err
r.recorder.Event(cluster, core.EventTypeWarning, "GenerateClusterfile", err.Error())
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 60}, err
}
fmt.Println(clusterfile)
if err := applyClusterfile(infra, clusterfile, getSealosVersion(cluster)); err != nil {
r.recorder.Event(cluster, core.EventTypeWarning, "ApplyClusterfile", err.Error())
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 60}, err
}
if cluster.Spec.SSH.PkData == "" {
cluster.Spec.SSH.PkData = infra.Spec.SSH.PkData
}
err = r.applier.ReconcileCluster(infra, hosts, cluster)
if err != nil {
logger.Error("reconcile cluster error: %v", err)
// update cluster status
cluster.Status.Status = infrav1.Running.String()
if err := r.Status().Update(ctx, cluster); err != nil {
r.recorder.Event(cluster, core.EventTypeWarning, "UpdateClusterStatus", err.Error())
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// Get private ip from metadata
func getPrivateIP(meta infrav1.Metadata) string {
for _, ip := range meta.IP {
if ip.IPType == infracommon.IPTypePrivate {
return ip.IPValue
}
}
return ""
}
// Generate Clusterfile by infra and cluster
func generateClusterfile(infra *infrav1.Infra, cluster *v1.Cluster) (string, error) {
new := cluster.DeepCopy()
new.CreationTimestamp = metav1.Time{}
new.Spec.SSH = infra.Spec.SSH
new.Spec.SSH.User = defaultUser
for _, host := range infra.Spec.Hosts {
for _, meta := range host.Metadata {
privateIP := getPrivateIP(meta)
if privateIP == "" {
continue
}
new.Spec.Hosts = append(new.Spec.Hosts, v1beta1.Host{
IPS: []string{privateIP},
Roles: host.Roles,
})
}
}
// convert cluster to yaml
clusterfile, err := yaml.Marshal(new)
if err != nil {
return "", fmt.Errorf("marshal cluster [%s] to yaml failed: %v", new.Name, err)
}
return string(clusterfile), nil
}
// Get master0 public ip from infra
func getMaster0PublicIP(infra *infrav1.Infra) string {
for _, host := range infra.Spec.Hosts {
for _, meta := range host.Metadata {
for _, ip := range meta.IP {
if ip.IPType == infracommon.IPTypePublic && host.Roles[0] == v1beta1.MASTER {
return ip.IPValue
}
}
}
}
return ""
}
// get sealos version from cluster
func getSealosVersion(cluster *v1.Cluster) string {
if v, ok := cluster.Annotations["sealos.io/sealos/version"]; ok && v != "" {
return v
}
return defaultSealosVersion
}
// Apply clusterfile on infra
func applyClusterfile(infra *infrav1.Infra, clusterfile, sealosVersion string) error {
s := &v1beta1.SSH{
User: infra.Spec.SSH.User,
PkData: infra.Spec.SSH.PkData,
}
c := ssh.NewSSHClient(s, true)
EIP := getMaster0PublicIP(infra)
if EIP == "" {
return fmt.Errorf("get master0 public ip failed")
}
if err := ssh.WaitSSHReady(c, 5, EIP); err != nil {
return fmt.Errorf("wait ssh ready failed: %v", err)
}
createClusterfile := fmt.Sprintf("echo '%s' > /root/Clusterfile", clusterfile)
downloadSealos := fmt.Sprintf(downloadSealosCmd, sealosVersion, sealosVersion, sealosVersion)
cmds := []string{createClusterfile, downloadSealos, applyClusterfileCmd}
if err := c.CmdAsync(EIP, cmds...); err != nil {
return fmt.Errorf("write clusterfile to remote failed: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
driver, err := drivers.NewDriver()
@@ -98,7 +207,7 @@ func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return fmt.Errorf("cluster controller new driver failed: %v", err)
}
r.driver = driver
r.applier = applier.NewApplier()
r.recorder = mgr.GetEventRecorderFor("sealos-cluster-controller")
return ctrl.NewControllerManagedBy(mgr).
For(&v1.Cluster{}).
@@ -3,37 +3,38 @@ package controllers
import (
"testing"
"github.com/labring/sealos/controllers/cluster/applier"
"github.com/labring/sealos/controllers/infra/drivers"
ctrl "sigs.k8s.io/controller-runtime"
infrav1 "github.com/labring/sealos/controllers/infra/api/v1"
v1 "github.com/labring/sealos/controllers/cluster/api/v1"
)
func TestClusterReconciler_SetupWithManager(t *testing.T) {
func Test_generateClusterfile(t *testing.T) {
type args struct {
mgr ctrl.Manager
infra *infrav1.Infra
cluster *v1.Cluster
}
var tests = []struct {
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
"test for setup",
args{
mgr: nil,
},
"test for generate clusterfile",
args{},
"",
false,
},
}
for _, tt := range tests {
driver, _ := drivers.NewDriver()
t.Run(tt.name, func(t *testing.T) {
r := &ClusterReconciler{
driver: driver,
applier: applier.NewApplier(),
got, err := generateClusterfile(tt.args.infra, tt.args.cluster)
if (err != nil) != tt.wantErr {
t.Errorf("generateClusterfile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err := r.SetupWithManager(tt.args.mgr); (err != nil) != tt.wantErr {
t.Errorf("SetupWithManager() error = %v, wantErr %v", err, tt.wantErr)
if got != tt.want {
t.Errorf("generateClusterfile() got = %v, want %v", got, tt.want)
}
})
}
@@ -99,7 +99,7 @@ func (r *InfraReconciler) SetupWithManager(mgr ctrl.Manager) error {
}
r.driver = driver
r.applier = &drivers.Applier{}
r.recorder = mgr.GetEventRecorderFor("salos-infra-controller")
r.recorder = mgr.GetEventRecorderFor("sealos-infra-controller")
return ctrl.NewControllerManagedBy(mgr).
For(&infrav1.Infra{}).
@@ -210,6 +210,7 @@ func (d Driver) CreateKeyPair(infra *v1.Infra) error {
if infra.Spec.SSH.PkName != "" {
return nil
}
mutex.Lock()
defer mutex.Unlock()
client := d.Client