diff --git a/go.mod b/go.mod index 6be1ffbb..9ff73461 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( k8s.io/klog/v2 v2.130.1 kmodules.xyz/client-go v0.30.13 kmodules.xyz/custom-resources v0.30.0 - kubedb.dev/apimachinery v0.47.1-0.20240925065855-706baeb42442 + kubedb.dev/apimachinery v0.47.1-0.20240926114257-108f2b41a885 sigs.k8s.io/controller-runtime v0.18.4 xorm.io/xorm v1.3.6 ) @@ -46,6 +46,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/cert-manager/cert-manager v1.15.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/eapache/go-resiliency v1.4.0 // indirect diff --git a/go.sum b/go.sum index 9d00a17d..db1fa0cc 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86c github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -802,8 +804,8 @@ kmodules.xyz/monitoring-agent-api v0.29.0 h1:gpFl6OZrlMLb/ySMHdREI9EwGtnJ91oZBn9 kmodules.xyz/monitoring-agent-api v0.29.0/go.mod h1:iNbvaMTgVFOI5q2LJtGK91j4Dmjv4ZRiRdasGmWLKQI= kmodules.xyz/offshoot-api v0.30.0 h1:dq9F93pu4Q8rL9oTcCk+vGGy8vpS7RNt0GSwx7Bvhec= kmodules.xyz/offshoot-api v0.30.0/go.mod h1:o9VoA3ImZMDBp3lpLb8+kc2d/KBxioRwCpaKDfLIyDw= -kubedb.dev/apimachinery v0.47.1-0.20240925065855-706baeb42442 h1:MgXbQzSri2ibP/MJFNn9lajy+CzeW5ajsSuMS5bf6mI= -kubedb.dev/apimachinery v0.47.1-0.20240925065855-706baeb42442/go.mod h1:iD6XKg9Blvfd9iYEO0N9GKiSz6r+yzEPZnfkYdESNG4= +kubedb.dev/apimachinery v0.47.1-0.20240926114257-108f2b41a885 h1:Tgo6RxT0xOwbGox9u92GFUj4/1cQYQ+E2SMPm+TWiKQ= +kubedb.dev/apimachinery v0.47.1-0.20240926114257-108f2b41a885/go.mod h1:oyCAmVdJXFLs2jfSqjMFV5pcKdt0v4U4hIuVAaLYv+Q= kubeops.dev/petset v0.0.5-0.20240603165102-e2d9decb8abe h1:uWyps3VIDFwGuL0yQa0eMGaLg4ofVwpy59U14Trxnz8= kubeops.dev/petset v0.0.5-0.20240603165102-e2d9decb8abe/go.mod h1:A15vh0r979NsvL65DTIZKWsa/NoX9VapHBAEw1ZsdYI= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= diff --git a/solr/api.go b/solr/api.go index a7281913..dea0f8ae 100644 --- a/solr/api.go +++ b/solr/api.go @@ -30,12 +30,19 @@ const ( ActionRestore = "RESTORE" ActionCreate = "CREATE" ActionDeleteBackup = "DELETEBACKUP" + AddRole = "ADDROLE" + RemoveRole = "REMOVEROLE" Name = "name" + Role = "role" + Node = "node" Location = "location" Repository = "repository" Collection = "collection" Async = "async" + Replica = "replica" + MoveReplica = "MOVEREPLICA" PurgeUnused = "purgeUnused" + TargetNode = "targetNode" BackupId = "backupId" DeleteStatus = "DELETESTATUS" RequestStatus = "REQUESTSTATUS" @@ -60,4 +67,8 @@ type SLClient interface { GetClient() *resty.Client GetLog() logr.Logger DecodeBackupResponse(data map[string]interface{}, collection string) ([]byte, error) + MoveReplica(target string, replica string, collection string, async string) (*Response, error) + BalanceReplica(async string) (*Response, error) + AddRole(role, node string) (*Response, error) + RemoveRole(role, node string) (*Response, error) } diff --git a/solr/client.go b/solr/client.go index 483b76ac..595ff38d 100644 --- a/solr/client.go +++ b/solr/client.go @@ -76,3 +76,29 @@ type CreateParams struct { NumShards int `json:"numShards,omitempty" yaml:"numShards,omitempty"` ReplicationFactor int `json:"replicationFactor,omitempty" yaml:"replicationFactor,omitempty"` } + +type MoveReplicaInfo struct { + Replica string `json:"replica,omitempty" yaml:"replica,omitempty"` + TargetNode string `json:"targetNode,omitempty" yaml:"targetNode,omitempty"` + Async string `json:"async,omitempty" yaml:"async,omitempty"` +} + +type MoveReplicaParams struct { + MoveReplica MoveReplicaInfo `json:"move-replica,omitempty" yaml:"move-replica,omitempty"` +} + +type BalanceReplica struct { + WaitForFinalState bool `json:"waitForFinalState,omitempty" yaml:"waitForFinalState,omitempty"` + Async string `json:"async,omitempty" yaml:"async,omitempty"` +} + +type CoreList struct { + coreName string + collection string +} + +type UpdateList struct { + target string + replica string + collection string +} diff --git a/solr/kubedb_client_builder.go b/solr/kubedb_client_builder.go index 474c18ad..0c481275 100644 --- a/solr/kubedb_client_builder.go +++ b/solr/kubedb_client_builder.go @@ -18,11 +18,15 @@ package solr import ( "context" + "crypto/tls" + "crypto/x509" "errors" "net" "net/http" "time" + "k8s.io/klog/v2" + "github.com/Masterminds/semver/v3" gerr "github.com/pkg/errors" core "k8s.io/api/core/v1" @@ -75,7 +79,7 @@ func (o *KubeDBClientBuilder) WithContext(ctx context.Context) *KubeDBClientBuil func (o *KubeDBClientBuilder) GetSolrClient() (*Client, error) { if o.podName != "" { - o.url = o.GetHostPath(o.db) + o.url = fmt.Sprintf("%v://%s.%s.%s.svc.cluster.local:%d", o.db.GetConnectionScheme(), o.podName, o.db.GoverningServiceName(), o.db.GetNamespace(), kubedb.SolrRestPort) } if o.url == "" { o.url = o.GetHostPath(o.db) @@ -99,6 +103,41 @@ func (o *KubeDBClientBuilder) GetSolrClient() (*Client, error) { log: o.log, } + // If EnableSSL is true set tls config, + // provide client certs and root CA + if o.db.Spec.EnableSSL { + var certSecret core.Secret + err := o.kc.Get(o.ctx, types.NamespacedName{ + Namespace: o.db.Namespace, + Name: o.db.GetCertSecretName(api.SolrClientCert), + }, &certSecret) + if err != nil { + klog.Error(err, "failed to get serverCert secret") + return nil, err + } + + // get tls cert, clientCA and rootCA for tls config + // use server cert ca for rootca as issuer ref is not taken into account + clientCA := x509.NewCertPool() + rootCA := x509.NewCertPool() + + crt, err := tls.X509KeyPair(certSecret.Data[core.TLSCertKey], certSecret.Data[core.TLSPrivateKeyKey]) + if err != nil { + klog.Error(err, "failed to create certificate for TLS config") + return nil, err + } + clientCA.AppendCertsFromPEM(certSecret.Data[kubedb.CACert]) + rootCA.AppendCertsFromPEM(certSecret.Data[kubedb.CACert]) + + config.transport.TLSClientConfig = &tls.Config{ + Certificates: []tls.Certificate{crt}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCA, + RootCAs: rootCA, + MaxVersion: tls.VersionTLS13, + } + } + var authSecret core.Secret if !o.db.Spec.DisableSecurity { err := o.kc.Get(o.ctx, types.NamespacedName{ diff --git a/solr/solrv8.go b/solr/solrv8.go index ddc0a861..7ab34862 100644 --- a/solr/solrv8.go +++ b/solr/solrv8.go @@ -342,3 +342,91 @@ func (sc *SLClientV8) DecodeBackupResponse(data map[string]interface{}, collecti return b, nil } + +func (sc *SLClientV8) MoveReplica(target string, replica string, collection string, async string) (*Response, error) { + sc.Config.log.V(5).Info(fmt.Sprintf("Move replica %v of collection %v to target node %v", replica, collection, target)) + req := sc.Client.R().SetDoNotParseResponse(true) + params := map[string]string{ + Action: MoveReplica, + Collection: collection, + Replica: replica, + TargetNode: target, + Async: async, + } + req.SetQueryParams(params) + //req.SetHeader("Content-Type", "application/json") + //moveReplica := &MoveReplicaParams{ + // MoveReplica: MoveReplicaInfo{ + // TargetNode: target, + // Replica: replica, + // Async: async, + // }, + //} + //req.SetBody(moveReplica) + res, err := req.Post("/solr/admin/collections") + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to move replica") + return nil, err + } + + moveReplicaResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return moveReplicaResponse, nil +} + +func (sc *SLClientV8) BalanceReplica(async string) (*Response, error) { + return nil, nil +} + +func (sc *SLClientV8) AddRole(role, node string) (*Response, error) { + sc.Config.log.V(5).Info(fmt.Sprintf("Add role %s in node %s", role, node)) + req := sc.Client.R().SetDoNotParseResponse(true) + req.SetHeader("Content-Type", "application/json") + params := map[string]string{ + Action: AddRole, + Node: node, + Role: role, + } + req.SetQueryParams(params) + + res, err := req.Get("/solr/admin/collections") + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to add a role") + return nil, err + } + + backupResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return backupResponse, nil +} + +func (sc *SLClientV8) RemoveRole(role, node string) (*Response, error) { + sc.Config.log.V(5).Info(fmt.Sprintf("Remove role %s in node %s", role, node)) + req := sc.Client.R().SetDoNotParseResponse(true) + req.SetHeader("Content-Type", "application/json") + params := map[string]string{ + Action: RemoveRole, + Node: node, + Role: role, + } + req.SetQueryParams(params) + + res, err := req.Get("/solr/admin/collections") + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to remove a role") + return nil, err + } + + backupResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return backupResponse, nil +} diff --git a/solr/solrv9.go b/solr/solrv9.go index bd6604b3..008853b5 100644 --- a/solr/solrv9.go +++ b/solr/solrv9.go @@ -330,3 +330,102 @@ func (sc *SLClientV9) DecodeBackupResponse(data map[string]interface{}, collecti klog.Info(fmt.Sprintf("Response for collection %s\n%v", collection, string(b))) return b, nil } + +func (sc *SLClientV9) MoveReplica(target string, replica string, collection string, async string) (*Response, error) { + sc.Config.log.V(5).Info(fmt.Sprintf("Move replica %v of collection %v to target node %v", replica, collection, target)) + req := sc.Client.R().SetDoNotParseResponse(true) + req.SetHeader("Content-Type", "application/json") + moveReplica := &MoveReplicaParams{ + MoveReplica: MoveReplicaInfo{ + TargetNode: target, + Replica: replica, + Async: async, + }, + } + req.SetBody(moveReplica) + res, err := req.Post(fmt.Sprintf("/api/collections/%s", collection)) + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to move replica") + return nil, err + } + + moveReplicaResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return moveReplicaResponse, nil +} + +func (sc *SLClientV9) BalanceReplica(async string) (*Response, error) { + sc.Config.log.V(5).Info("Balance replica") + req := sc.Client.R().SetDoNotParseResponse(true) + req.SetHeader("Content-Type", "application/json") + balanceReplica := &BalanceReplica{ + WaitForFinalState: true, + Async: async, + } + req.SetBody(balanceReplica) + res, err := req.Post("/api/cluster/replicas/balance") + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to move replica") + return nil, err + } + + moveReplicaResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return moveReplicaResponse, nil +} + +func (sc *SLClientV9) AddRole(role, node string) (*Response, error) { + sc.Config.log.V(5).Info(fmt.Sprintf("Add role %s in node %s", role, node)) + req := sc.Client.R().SetDoNotParseResponse(true) + req.SetHeader("Content-Type", "application/json") + params := map[string]string{ + Action: AddRole, + Node: node, + Role: role, + } + req.SetQueryParams(params) + + res, err := req.Get("/solr/admin/collections") + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to add a role") + return nil, err + } + + backupResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return backupResponse, nil +} + +func (sc *SLClientV9) RemoveRole(role, node string) (*Response, error) { + sc.Config.log.V(5).Info(fmt.Sprintf("Remove role %s in node %s", role, node)) + req := sc.Client.R().SetDoNotParseResponse(true) + req.SetHeader("Content-Type", "application/json") + params := map[string]string{ + Action: RemoveRole, + Node: node, + Role: role, + } + req.SetQueryParams(params) + + res, err := req.Get("/solr/admin/collections") + if err != nil { + sc.Config.log.Error(err, "Failed to send http request to remove a role") + return nil, err + } + + backupResponse := &Response{ + Code: res.StatusCode(), + header: res.Header(), + body: res.RawBody(), + } + return backupResponse, nil +} diff --git a/solr/util.go b/solr/util.go index 89822ee8..3f450774 100644 --- a/solr/util.go +++ b/solr/util.go @@ -20,6 +20,13 @@ import ( "encoding/json" "fmt" "io" + "sort" + "strings" + "sync" + "time" + + "k8s.io/klog/v2" + dbapi "kubedb.dev/apimachinery/apis/kubedb/v1alpha2" "github.com/pkg/errors" ) @@ -140,3 +147,454 @@ func (sc *Client) SearchCollection(collections []string) bool { } return false } + +func (sc *Client) CheckupStatus(async string) error { + var wg sync.WaitGroup + wg.Add(1) + var errr error + go func() { + defer wg.Done() + asyncId := async + for { + resp, err := sc.RequestStatus(asyncId) + if err != nil { + klog.Error(fmt.Sprintf("Failed to get response for asyncId %s. Error: %v", asyncId, err)) + errr = err + return + } + + responseBody, err := sc.DecodeResponse(resp) + if err != nil { + klog.Error(fmt.Sprintf("Failed to decode response for asyncId %s. Error: %v", asyncId, err)) + errr = err + return + } + + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error(fmt.Sprintf("status is non zero while checking status for asyncId %s. Error %v", asyncId, err)) + errr = err + return + } + + state, err := sc.GetAsyncStatus(responseBody) + if err != nil { + klog.Error(fmt.Sprintf("status is non zero while checking state of async for asyncId %s. Error %v", asyncId, err)) + errr = err + return + } + klog.Info(fmt.Sprintf("State for asyncid %v is %v\n", asyncId, state)) + if state == "completed" { + klog.Info("Status is completed for ", asyncId) + err = sc.FlushAsyncStatus(asyncId) + if err != nil { + errr = err + return + } + return + } else if state == "failed" { + klog.Info(fmt.Sprintf("API call for asyncId %s failed", asyncId)) + err = sc.FlushAsyncStatus(asyncId) + if err != nil { + errr = err + return + } + errr = fmt.Errorf("response for asyncid %v. failed with response %v", asyncId, responseBody) + break + } else if state == "notfound" { + klog.Info(fmt.Sprintf("API call for asyncid %s not found", asyncId)) + break + } + time.Sleep(10 * time.Second) + } + }() + wg.Wait() + return errr +} + +func (sc *Client) FlushAsyncStatus(asyncId string) error { + resp, err := sc.FlushStatus(asyncId) + if err != nil { + return err + } + + responseBody, err := sc.DecodeResponse(resp) + if err != nil { + return err + } + + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error("status is non zero while flushing status", err) + return err + } + + return nil +} + +func (sc *Client) CleanupAsync(async string) error { + var wg sync.WaitGroup + wg.Add(1) + var errr error + go func() { + defer wg.Done() + asyncId := async + for { + resp, err := sc.RequestStatus(asyncId) + if err != nil { + klog.Error(fmt.Sprintf("Failed to get response for asyncId %s. Error: %v", asyncId, err)) + errr = err + break + } + + responseBody, err := sc.DecodeResponse(resp) + if err != nil { + klog.Error(fmt.Sprintf("Failed to decode response for asyncId %s. Error: %v", asyncId, err)) + errr = err + break + } + + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error(fmt.Sprintf("status is non zero while checking status for asyncId %s. Error %v", asyncId, err)) + errr = err + break + } + + state, err := sc.GetAsyncStatus(responseBody) + if err != nil { + klog.Error(fmt.Sprintf("status is non zero while checking state of async for asyncId %s. Error %v", asyncId, err)) + errr = err + break + } + klog.Info(fmt.Sprintf("State for asyncid %v is %v\n", asyncId, state)) + if state == "completed" || state == "notfound" || state == "failed" { + err := sc.FlushAsyncStatus(asyncId) + if err != nil { + klog.Error(fmt.Sprintf("Failed to flush api call for asyncId %s. Error %v", asyncId, err)) + time.Sleep(20 * time.Second) + continue + } + break + } + time.Sleep(10 * time.Second) + } + }() + wg.Wait() + return errr +} + +func (sc *Client) Balance() error { + var errr error + async := "balance-replica" + err := sc.CleanupAsync(async) + if err != nil { + errr = err + } else { + klog.Info(fmt.Sprintf("Cleanup async successful for %v", async)) + } + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + resp, err := sc.BalanceReplica(async) + if err != nil { + klog.Error(fmt.Errorf("failed to do balance request. err %v", err)) + errr = err + } + responseBody, err := sc.DecodeResponse(resp) + if err != nil { + klog.Error(fmt.Errorf("failed to decode response for async %s, err %v", async, err)) + errr = err + } + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error(fmt.Errorf("failed to decode response for async %s, err %v", async, err)) + errr = err + } + + err = sc.CheckupStatus(async) + if err != nil { + errr = err + } + }() + wg.Wait() + return errr +} + +func (sc *Client) Run(lst []UpdateList) error { + var errr error + var wg sync.WaitGroup + for _, x := range lst { + target := x.target + replica := x.replica + collection := x.collection + async := fmt.Sprintf("%s-%s-%s", replica, collection, target) + err := sc.CleanupAsync(async) + if err != nil { + errr = err + } + klog.Info(fmt.Sprintf("Cleanup async successful for %v", async)) + + wg.Add(1) + go func() { + defer wg.Done() + resp, err := sc.MoveReplica(target, replica, collection, async) + if err != nil { + klog.Error(fmt.Errorf("failed to do request for target %s, replica %s, collection %s, err %v", target, replica, collection, err)) + errr = err + } + responseBody, err := sc.DecodeResponse(resp) + if err != nil { + klog.Error(fmt.Errorf("failed to decode response for target %s, replica %s, collection %s, err %v", target, replica, collection, err)) + errr = err + } + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error(fmt.Errorf("failed to decode response for target %s, replica %s, collection %s, err %v, responsebody %v", target, replica, collection, err, responseBody)) + errr = err + } + + err = sc.CheckupStatus(async) + if err != nil { + errr = err + } + }() + } + wg.Wait() + return errr +} + +func (sc *Client) Down(nodeList []string, x int, mp map[string][]CoreList) error { + n := len(nodeList) + ls2 := nodeList[n-x:] + ls1 := nodeList[:n-x] + fmt.Println("ls1 ", ls1) + fmt.Println("ls2 ", ls2) + ar := make([]UpdateList, 0) + for _, node := range ls2 { + for _, core := range mp[node] { + id := -1 + mx := 1000000000 + for j, l1 := range ls1 { + if len(mp[l1]) < mx { + mx = len(mp[l1]) + id = j + } + } + ar = append(ar, UpdateList{ + target: ls1[id], + replica: core.coreName, + collection: core.collection, + }) + mp[ls1[id]] = append(mp[ls1[id]], core) + fmt.Println(core.coreName, core.collection, ls1[id]) + } + } + err := sc.Run(ar) + return err +} +func (sc *Client) Up(nodeList []string, mp map[string][]CoreList) error { + for _, x := range nodeList { + if _, ok := mp[x]; !ok { + mp[x] = make([]CoreList, 0) + } + } + ar := make([]UpdateList, 0) + for { + mn := 10000000000 + minNode := "" + mx := -1 + maxNode := "" + for x, y := range mp { + n := len(y) + if mx < n { + mx = n + maxNode = x + } + + if mn > n { + mn = n + minNode = x + } + } + if maxNode == minNode || mx-mn <= 1 { + break + } + target := minNode + core := mp[maxNode][0].coreName + collection := mp[maxNode][0].collection + mp[minNode] = append(mp[minNode], mp[maxNode][0]) + mp[maxNode] = mp[maxNode][1:] + ar = append(ar, UpdateList{ + target: target, + replica: core, + collection: collection, + }) + fmt.Println(target, core, collection) + } + err := sc.Run(ar) + return err +} + +func (sc *Client) UpReplicaManual(db *dbapi.Solr) error { + + response, err := sc.GetClusterStatus() + if err != nil { + klog.Error(err) + return err + } + + responseBody, err := sc.DecodeResponse(response) + if err != nil { + klog.Error(err) + return err + } + + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error(err) + return err + } + + clusterInfo, ok := responseBody["cluster"].(map[string]interface{}) + if !ok { + klog.Error(fmt.Errorf("did not find cluster %v\n", responseBody)) + } + collections, ok := clusterInfo["collections"].(map[string]interface{}) + if !ok { + klog.Error("didn't find collections") + } + mp := make(map[string][]CoreList) + for collection, info := range collections { + collectionInfo := info.(map[string]interface{}) + shardInfo := collectionInfo["shards"].(map[string]interface{}) + for _, info := range shardInfo { + shardInfo := info.(map[string]interface{}) + replicaInfo := shardInfo["replicas"].(map[string]interface{}) + for core, info := range replicaInfo { + coreInfo := info.(map[string]interface{}) + nodeName := coreInfo["node_name"].(string) + if _, ok := mp[nodeName]; !ok { + mp[nodeName] = make([]CoreList, 0) + } + mp[nodeName] = append(mp[nodeName], CoreList{ + coreName: core, + collection: collection, + }) + } + } + } + + nodeList := make([]string, 0) + + liveNodes, ok := clusterInfo["live_nodes"] + if !ok { + return errors.New("Failed to get livenodes") + } + xx := liveNodes.([]interface{}) + for _, node := range xx { + x := node.(string) + nodeList = append(nodeList, x) + } + sort.Strings(nodeList) + fmt.Println(nodeList) + + if db.Spec.Topology != nil { + str := strings.Join([]string{db.Name, "data"}, "-") + mn := 10000000000 + mx := -1 + for i, node := range nodeList { + if strings.Contains(node, str) { + mn = min(mn, i) + mx = max(mx, i) + } + } + nodeList = nodeList[mn : mx+1] + fmt.Println("nodes ", mx-mn+1, nodeList) + } + + err = sc.Up(nodeList, mp) + return err +} + +func (sc *Client) BalanceReplicaManual(db *dbapi.Solr, desired int) error { + + response, err := sc.GetClusterStatus() + if err != nil { + klog.Error(err) + return err + } + + responseBody, err := sc.DecodeResponse(response) + if err != nil { + klog.Error(err) + return err + } + + _, err = sc.GetResponseStatus(responseBody) + if err != nil { + klog.Error(err) + return err + } + + clusterInfo, ok := responseBody["cluster"].(map[string]interface{}) + if !ok { + klog.Error(fmt.Errorf("did not find cluster %v\n", responseBody)) + } + collections, ok := clusterInfo["collections"].(map[string]interface{}) + if !ok { + klog.Error("didn't find collections") + } + mp := make(map[string][]CoreList) + for collection, info := range collections { + collectionInfo := info.(map[string]interface{}) + shardInfo := collectionInfo["shards"].(map[string]interface{}) + for _, info := range shardInfo { + shardInfo := info.(map[string]interface{}) + replicaInfo := shardInfo["replicas"].(map[string]interface{}) + for core, info := range replicaInfo { + coreInfo := info.(map[string]interface{}) + nodeName := coreInfo["node_name"].(string) + if _, ok := mp[nodeName]; !ok { + mp[nodeName] = make([]CoreList, 0) + } + mp[nodeName] = append(mp[nodeName], CoreList{ + coreName: core, + collection: collection, + }) + } + } + } + + nodeList := make([]string, 0) + + liveNodes, ok := clusterInfo["live_nodes"] + if !ok { + return errors.New("Failed to get livenodes") + } + xx := liveNodes.([]interface{}) + for _, node := range xx { + x := node.(string) + nodeList = append(nodeList, x) + } + sort.Strings(nodeList) + fmt.Println(nodeList) + + if db.Spec.Topology != nil { + str := strings.Join([]string{db.Name, "data"}, "-") + mn := 10000000000 + mx := -1 + for i, node := range nodeList { + if strings.Contains(node, str) { + mn = min(mn, i) + mx = max(mx, i) + } + } + nodeList = nodeList[mn : mx+1] + fmt.Println("nodes ", mx-mn+1, nodeList) + } + + err = sc.Down(nodeList, desired, mp) + return err +} diff --git a/vendor/github.com/coreos/go-semver/LICENSE b/vendor/github.com/coreos/go-semver/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/coreos/go-semver/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/coreos/go-semver/NOTICE b/vendor/github.com/coreos/go-semver/NOTICE new file mode 100644 index 00000000..23a0ada2 --- /dev/null +++ b/vendor/github.com/coreos/go-semver/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2018 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/go-semver/semver/semver.go b/vendor/github.com/coreos/go-semver/semver/semver.go new file mode 100644 index 00000000..eb9fb7ff --- /dev/null +++ b/vendor/github.com/coreos/go-semver/semver/semver.go @@ -0,0 +1,296 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// 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. + +// Semantic Versions http://semver.org +package semver + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +type Version struct { + Major int64 + Minor int64 + Patch int64 + PreRelease PreRelease + Metadata string +} + +type PreRelease string + +func splitOff(input *string, delim string) (val string) { + parts := strings.SplitN(*input, delim, 2) + + if len(parts) == 2 { + *input = parts[0] + val = parts[1] + } + + return val +} + +func New(version string) *Version { + return Must(NewVersion(version)) +} + +func NewVersion(version string) (*Version, error) { + v := Version{} + + if err := v.Set(version); err != nil { + return nil, err + } + + return &v, nil +} + +// Must is a helper for wrapping NewVersion and will panic if err is not nil. +func Must(v *Version, err error) *Version { + if err != nil { + panic(err) + } + return v +} + +// Set parses and updates v from the given version string. Implements flag.Value +func (v *Version) Set(version string) error { + metadata := splitOff(&version, "+") + preRelease := PreRelease(splitOff(&version, "-")) + dotParts := strings.SplitN(version, ".", 3) + + if len(dotParts) != 3 { + return fmt.Errorf("%s is not in dotted-tri format", version) + } + + if err := validateIdentifier(string(preRelease)); err != nil { + return fmt.Errorf("failed to validate pre-release: %v", err) + } + + if err := validateIdentifier(metadata); err != nil { + return fmt.Errorf("failed to validate metadata: %v", err) + } + + parsed := make([]int64, 3) + + for i, v := range dotParts[:3] { + val, err := strconv.ParseInt(v, 10, 64) + parsed[i] = val + if err != nil { + return err + } + } + + v.Metadata = metadata + v.PreRelease = preRelease + v.Major = parsed[0] + v.Minor = parsed[1] + v.Patch = parsed[2] + return nil +} + +func (v Version) String() string { + var buffer bytes.Buffer + + fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch) + + if v.PreRelease != "" { + fmt.Fprintf(&buffer, "-%s", v.PreRelease) + } + + if v.Metadata != "" { + fmt.Fprintf(&buffer, "+%s", v.Metadata) + } + + return buffer.String() +} + +func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error { + var data string + if err := unmarshal(&data); err != nil { + return err + } + return v.Set(data) +} + +func (v Version) MarshalJSON() ([]byte, error) { + return []byte(`"` + v.String() + `"`), nil +} + +func (v *Version) UnmarshalJSON(data []byte) error { + l := len(data) + if l == 0 || string(data) == `""` { + return nil + } + if l < 2 || data[0] != '"' || data[l-1] != '"' { + return errors.New("invalid semver string") + } + return v.Set(string(data[1 : l-1])) +} + +// Compare tests if v is less than, equal to, or greater than versionB, +// returning -1, 0, or +1 respectively. +func (v Version) Compare(versionB Version) int { + if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 { + return cmp + } + return preReleaseCompare(v, versionB) +} + +// Equal tests if v is equal to versionB. +func (v Version) Equal(versionB Version) bool { + return v.Compare(versionB) == 0 +} + +// LessThan tests if v is less than versionB. +func (v Version) LessThan(versionB Version) bool { + return v.Compare(versionB) < 0 +} + +// Slice converts the comparable parts of the semver into a slice of integers. +func (v Version) Slice() []int64 { + return []int64{v.Major, v.Minor, v.Patch} +} + +func (p PreRelease) Slice() []string { + preRelease := string(p) + return strings.Split(preRelease, ".") +} + +func preReleaseCompare(versionA Version, versionB Version) int { + a := versionA.PreRelease + b := versionB.PreRelease + + /* Handle the case where if two versions are otherwise equal it is the + * one without a PreRelease that is greater */ + if len(a) == 0 && (len(b) > 0) { + return 1 + } else if len(b) == 0 && (len(a) > 0) { + return -1 + } + + // If there is a prerelease, check and compare each part. + return recursivePreReleaseCompare(a.Slice(), b.Slice()) +} + +func recursiveCompare(versionA []int64, versionB []int64) int { + if len(versionA) == 0 { + return 0 + } + + a := versionA[0] + b := versionB[0] + + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursiveCompare(versionA[1:], versionB[1:]) +} + +func recursivePreReleaseCompare(versionA []string, versionB []string) int { + // A larger set of pre-release fields has a higher precedence than a smaller set, + // if all of the preceding identifiers are equal. + if len(versionA) == 0 { + if len(versionB) > 0 { + return -1 + } + return 0 + } else if len(versionB) == 0 { + // We're longer than versionB so return 1. + return 1 + } + + a := versionA[0] + b := versionB[0] + + aInt := false + bInt := false + + aI, err := strconv.Atoi(versionA[0]) + if err == nil { + aInt = true + } + + bI, err := strconv.Atoi(versionB[0]) + if err == nil { + bInt = true + } + + // Numeric identifiers always have lower precedence than non-numeric identifiers. + if aInt && !bInt { + return -1 + } else if !aInt && bInt { + return 1 + } + + // Handle Integer Comparison + if aInt && bInt { + if aI > bI { + return 1 + } else if aI < bI { + return -1 + } + } + + // Handle String Comparison + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursivePreReleaseCompare(versionA[1:], versionB[1:]) +} + +// BumpMajor increments the Major field by 1 and resets all other fields to their default values +func (v *Version) BumpMajor() { + v.Major += 1 + v.Minor = 0 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// BumpMinor increments the Minor field by 1 and resets all other fields to their default values +func (v *Version) BumpMinor() { + v.Minor += 1 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// BumpPatch increments the Patch field by 1 and resets all other fields to their default values +func (v *Version) BumpPatch() { + v.Patch += 1 + v.PreRelease = PreRelease("") + v.Metadata = "" +} + +// validateIdentifier makes sure the provided identifier satisfies semver spec +func validateIdentifier(id string) error { + if id != "" && !reIdentifier.MatchString(id) { + return fmt.Errorf("%s is not a valid semver identifier", id) + } + return nil +} + +// reIdentifier is a regular expression used to check that pre-release and metadata +// identifiers satisfy the spec requirements +var reIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`) diff --git a/vendor/github.com/coreos/go-semver/semver/sort.go b/vendor/github.com/coreos/go-semver/semver/sort.go new file mode 100644 index 00000000..e256b41a --- /dev/null +++ b/vendor/github.com/coreos/go-semver/semver/sort.go @@ -0,0 +1,38 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// 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 semver + +import ( + "sort" +) + +type Versions []*Version + +func (s Versions) Len() int { + return len(s) +} + +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s Versions) Less(i, j int) bool { + return s[i].LessThan(*s[j]) +} + +// Sort sorts the given slice of Version +func Sort(versions []*Version) { + sort.Sort(Versions(versions)) +} diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/constants.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/constants.go index 1d3540e9..19bfafa5 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/constants.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/constants.go @@ -940,6 +940,11 @@ const ( SolrConfAllowPathsValue = "" SolrConfSolrCloudKey = "solrcloud" SolrConfShardHandlerFactoryKey = "shardHandlerFactory" + SolrJavaMem = "-Xms3g -Xmx3g" + SolrKeystorePassKey = "keystore-secret" + SolrServerKeystorePath = "/var/solr/etc/keystore.p12" + SolrServerTruststorePath = "/var/solr/etc/truststore.p12" + SolrTLSMountPath = "/var/solr/etc" ProxyDeploymentName = "s3proxy" ProxyServiceName = "proxy-svc" diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/openapi_generated.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/openapi_generated.go index d8dcaceb..f4cc74a8 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/openapi_generated.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/openapi_generated.go @@ -34603,6 +34603,13 @@ func schema_apimachinery_apis_kubedb_v1alpha2_SolrSpec(ref common.ReferenceCallb Format: "", }, }, + "clientAuthSSL": { + SchemaProps: spec.SchemaProps{ + Description: "Client auth need or want", + Type: []string{"string"}, + Format: "", + }, + }, "tls": { SchemaProps: spec.SchemaProps{ Description: "TLS contains tls configurations for client and server.", @@ -34621,6 +34628,11 @@ func schema_apimachinery_apis_kubedb_v1alpha2_SolrSpec(ref common.ReferenceCallb Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), }, }, + "keystoreSecret": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, "authSecret": { SchemaProps: spec.SchemaProps{ Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_helpers.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_helpers.go index a54476ff..b91305a7 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_helpers.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_helpers.go @@ -19,6 +19,7 @@ package v1alpha2 import ( "context" "fmt" + "path/filepath" "sort" "strings" @@ -34,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" + kmapi "kmodules.xyz/client-go/api/v1" "kmodules.xyz/client-go/apiextensions" coreutil "kmodules.xyz/client-go/core/v1" meta_util "kmodules.xyz/client-go/meta" @@ -227,6 +229,14 @@ func (s solrStatsService) TLSConfig() *promapi.TLSConfig { return nil } +func (s *Solr) SetTLSDefaults() { + if s.Spec.TLS == nil || s.Spec.TLS.IssuerRef == nil { + return + } + s.Spec.TLS.Certificates = kmapi.SetMissingSecretNameForCertificate(s.Spec.TLS.Certificates, string(SolrServerCert), s.CertificateName(SolrServerCert)) + s.Spec.TLS.Certificates = kmapi.SetMissingSecretNameForCertificate(s.Spec.TLS.Certificates, string(SolrClientCert), s.CertificateName(SolrClientCert)) +} + func (s *Solr) StatsService() mona.StatsAccessor { return &solrStatsService{s} } @@ -244,6 +254,10 @@ func (s *Solr) SetDefaults() { s.Spec.DeletionPolicy = TerminationPolicyDelete } + if s.Spec.ClientAuthSSL != "need" && s.Spec.ClientAuthSSL != "want" { + s.Spec.ClientAuthSSL = "" + } + if s.Spec.StorageType == "" { s.Spec.StorageType = StorageTypeDurable } @@ -254,6 +268,12 @@ func (s *Solr) SetDefaults() { } } + if s.Spec.KeystoreSecret == nil { + s.Spec.KeystoreSecret = &v1.LocalObjectReference{ + Name: s.SolrSecretName("keystore-cred"), + } + } + if s.Spec.ZookeeperDigestSecret == nil { s.Spec.ZookeeperDigestSecret = &v1.LocalObjectReference{ Name: s.SolrSecretName("zk-digest"), @@ -348,6 +368,8 @@ func (s *Solr) SetDefaults() { } s.Spec.Monitor.SetDefaults() } + + s.SetTLSDefaults() } func (s *Solr) setDefaultContainerSecurityContext(slVersion *catalog.SolrVersion, podTemplate *ofst.PodTemplateSpec) { @@ -453,3 +475,36 @@ func (s *Solr) ReplicasAreReady(lister pslister.PetSetLister) (bool, string, err } return checkReplicasOfPetSet(lister.PetSets(s.Namespace), labels.SelectorFromSet(s.OffshootLabels()), expectedItems) } + +// CertificateName returns the default certificate name and/or certificate secret name for a certificate alias +func (s *Solr) CertificateName(alias SolrCertificateAlias) string { + return meta_util.NameWithSuffix(s.Name, fmt.Sprintf("%s-cert", string(alias))) +} + +// ClientCertificateCN returns the CN for a client certificate +func (s *Solr) ClientCertificateCN(alias SolrCertificateAlias) string { + return fmt.Sprintf("%s-%s", s.Name, string(alias)) +} + +// GetCertSecretName returns the secret name for a certificate alias if any, +// otherwise returns default certificate secret name for the given alias. +func (s *Solr) GetCertSecretName(alias SolrCertificateAlias) string { + if s.Spec.TLS != nil { + name, ok := kmapi.GetCertificateSecretName(s.Spec.TLS.Certificates, string(alias)) + if ok { + return name + } + } + return s.CertificateName(alias) +} + +// CertSecretVolumeName returns the CertSecretVolumeName +// Values will be like: client-certs, server-certs etc. +func (s *Solr) CertSecretVolumeName(alias SolrCertificateAlias) string { + return string(alias) + "-certs" +} + +// CertSecretVolumeMountPath returns the CertSecretVolumeMountPath +func (s *Solr) CertSecretVolumeMountPath(configDir string, cert string) string { + return filepath.Join(configDir, cert) +} diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_types.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_types.go index 950edb5d..1587bc34 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_types.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_types.go @@ -83,6 +83,9 @@ type SolrSpec struct { // To enable ssl for http layer EnableSSL bool `json:"enableSSL,omitempty"` + // Client auth need or want + ClientAuthSSL string `json:"clientAuthSSL,omitempty"` + // TLS contains tls configurations for client and server. // +optional TLS *kmapi.TLSConfig `json:"tls,omitempty"` @@ -95,6 +98,9 @@ type SolrSpec struct { // +optional ConfigSecret *core.LocalObjectReference `json:"configSecret,omitempty"` + // +optional + KeystoreSecret *core.LocalObjectReference `json:"keystoreSecret,omitempty"` + // +optional AuthSecret *core.LocalObjectReference `json:"authSecret,omitempty"` @@ -177,6 +183,17 @@ const ( SolrNodeRoleSet = "set" ) +// +kubebuilder:validation:Enum=ca;transport;http;client;server +type SolrCertificateAlias string + +const ( + SolrCACert SolrCertificateAlias = "ca" + SolrTransportCert SolrCertificateAlias = "transport" + SolrHTTPCert SolrCertificateAlias = "http" + SolrClientCert SolrCertificateAlias = "client" + SolrServerCert SolrCertificateAlias = "server" +) + //+kubebuilder:object:root=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_webhook.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_webhook.go index 69c3d4ae..4a64c8e8 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_webhook.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_webhook.go @@ -25,6 +25,7 @@ import ( catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1" "kubedb.dev/apimachinery/apis/kubedb" + "github.com/coreos/go-semver/semver" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -143,6 +144,13 @@ func (s *Solr) ValidateCreateOrUpdate() field.ErrorList { } } + version := semver.New(s.Spec.Version) + if version.Major == 8 && s.Spec.Topology != nil { + allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("enableSSL"), + s.Name, + ".spec.topology not supported for version 8")) + } + err := solrValidateModules(s) if err != nil { allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("solrmodules"), diff --git a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/zz_generated.deepcopy.go b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/zz_generated.deepcopy.go index bd3772fd..7e98f2fd 100644 --- a/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/zz_generated.deepcopy.go +++ b/vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/zz_generated.deepcopy.go @@ -5567,6 +5567,11 @@ func (in *SolrSpec) DeepCopyInto(out *SolrSpec) { *out = new(corev1.LocalObjectReference) **out = **in } + if in.KeystoreSecret != nil { + in, out := &in.KeystoreSecret, &out.KeystoreSecret + *out = new(corev1.LocalObjectReference) + **out = **in + } if in.AuthSecret != nil { in, out := &in.AuthSecret, &out.AuthSecret *out = new(corev1.LocalObjectReference) diff --git a/vendor/kubedb.dev/apimachinery/crds/kubedb.com_solrs.yaml b/vendor/kubedb.dev/apimachinery/crds/kubedb.com_solrs.yaml index bc510b15..ecd6f8b6 100644 --- a/vendor/kubedb.dev/apimachinery/crds/kubedb.com_solrs.yaml +++ b/vendor/kubedb.dev/apimachinery/crds/kubedb.com_solrs.yaml @@ -60,6 +60,8 @@ spec: type: string type: object x-kubernetes-map-type: atomic + clientAuthSSL: + type: string configSecret: properties: name: @@ -99,6 +101,13 @@ spec: format: int32 type: integer type: object + keystoreSecret: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic monitor: properties: agent: diff --git a/vendor/kubedb.dev/apimachinery/crds/ops.kubedb.com_solropsrequests.yaml b/vendor/kubedb.dev/apimachinery/crds/ops.kubedb.com_solropsrequests.yaml index aa82131f..605bb7ae 100644 --- a/vendor/kubedb.dev/apimachinery/crds/ops.kubedb.com_solropsrequests.yaml +++ b/vendor/kubedb.dev/apimachinery/crds/ops.kubedb.com_solropsrequests.yaml @@ -71,17 +71,139 @@ spec: type: string type: object x-kubernetes-map-type: atomic + horizontalScaling: + properties: + coordinator: + format: int32 + type: integer + data: + format: int32 + type: integer + node: + format: int32 + type: integer + overseer: + format: int32 + type: integer + type: object restart: type: object timeout: type: string + tls: + properties: + certificates: + items: + properties: + alias: + type: string + dnsNames: + items: + type: string + type: array + duration: + type: string + emailAddresses: + items: + type: string + type: array + ipAddresses: + items: + type: string + type: array + issuerRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + privateKey: + properties: + encoding: + enum: + - PKCS1 + - PKCS8 + type: string + type: object + renewBefore: + type: string + secretName: + type: string + subject: + properties: + countries: + items: + type: string + type: array + localities: + items: + type: string + type: array + organizationalUnits: + items: + type: string + type: array + organizations: + items: + type: string + type: array + postalCodes: + items: + type: string + type: array + provinces: + items: + type: string + type: array + serialNumber: + type: string + streetAddresses: + items: + type: string + type: array + type: object + uris: + items: + type: string + type: array + required: + - alias + type: object + type: array + issuerRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + remove: + type: boolean + rotateCertificates: + type: boolean + type: object type: enum: - UpdateVersion + - HorizontalScaling - VerticalScaling - VolumeExpansion - Reconfigure - Restart + - ReconfigureTLS type: string updateVersion: properties: diff --git a/vendor/modules.txt b/vendor/modules.txt index 843614e6..0b1704e4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -47,6 +47,9 @@ github.com/cert-manager/cert-manager/pkg/apis/meta/v1 # github.com/cespare/xxhash/v2 v2.3.0 ## explicit; go 1.11 github.com/cespare/xxhash/v2 +# github.com/coreos/go-semver v0.3.1 +## explicit; go 1.8 +github.com/coreos/go-semver/semver # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew @@ -1561,7 +1564,7 @@ kmodules.xyz/offshoot-api/api/v1 kmodules.xyz/offshoot-api/api/v1/conversion kmodules.xyz/offshoot-api/api/v2 kmodules.xyz/offshoot-api/util -# kubedb.dev/apimachinery v0.47.1-0.20240925065855-706baeb42442 +# kubedb.dev/apimachinery v0.47.1-0.20240926114257-108f2b41a885 ## explicit; go 1.22.1 kubedb.dev/apimachinery/apis kubedb.dev/apimachinery/apis/catalog