| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 solr_api |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "encoding/json" |
| "fmt" |
| solr "github.com/apache/solr-operator/api/v1beta1" |
| "io" |
| "k8s.io/apimachinery/pkg/api/errors" |
| "net/http" |
| "net/url" |
| ) |
| |
| const ( |
| HTTP_HEADERS_CONTEXT_KEY = "HTTP_HEADERS" |
| ) |
| |
| // Used to call a Solr pod over https when using a self-signed cert |
| // It's "insecure" but is only used for internal communication, such as getting cluster status |
| // so if you're worried about this, don't use a self-signed cert |
| var noVerifyTLSHttpClient *http.Client |
| var mTLSHttpClient *http.Client |
| |
| func SetNoVerifyTLSHttpClient(client *http.Client) { |
| noVerifyTLSHttpClient = client |
| } |
| |
| func SetMTLSHttpClient(client *http.Client) { |
| mTLSHttpClient = client |
| } |
| |
| type SolrAsyncResponse struct { |
| ResponseHeader SolrResponseHeader `json:"responseHeader"` |
| |
| // +optional |
| RequestId string `json:"requestId,omitempty"` |
| |
| // +optional |
| Status SolrAsyncStatus `json:"status,omitempty"` |
| |
| // +optional |
| Error *SolrErrorResponse `json:"error,omitempty"` |
| } |
| |
| type SolrResponseHeader struct { |
| Status int `json:"status"` |
| |
| QTime int `json:"QTime"` |
| } |
| |
| type SolrAsyncStatus struct { |
| // Possible states can be found here: https://github.com/apache/solr/blob/releases/lucene-solr%2F8.8.1/solr/solrj/src/java/org/apache/solr/client/solrj/response/RequestStatusState.java |
| // +optional |
| AsyncState string `json:"state,omitempty"` |
| |
| // +optional |
| Message string `json:"msg,omitempty"` |
| } |
| |
| type SolrAsyncStatusResponse struct { |
| ResponseHeader SolrResponseHeader `json:"responseHeader"` |
| |
| // +optional |
| Status SolrAsyncStatus `json:"status,omitempty"` |
| |
| // +optional |
| Error *SolrErrorResponse `json:"error,omitempty"` |
| } |
| |
| type SolrDeleteRequestStatus struct { |
| ResponseHeader SolrResponseHeader `json:"responseHeader"` |
| |
| // Status of the delete request |
| // +optional |
| Status string `json:"status,omitempty"` |
| |
| // +optional |
| Error *SolrErrorResponse `json:"error,omitempty"` |
| } |
| |
| type SolrCollectionsListing struct { |
| ResponseHeader SolrResponseHeader `json:"responseHeader"` |
| |
| // +optional |
| Collections []string `json:"collections,omitempty"` |
| |
| // +optional |
| Error *SolrErrorResponse `json:"error,omitempty"` |
| } |
| |
| func CheckAsyncRequest(ctx context.Context, cloud *solr.SolrCloud, asyncId string) (asyncState string, message string, err error) { |
| asyncStatus := &SolrAsyncStatusResponse{} |
| |
| queryParams := url.Values{} |
| queryParams.Set("action", "REQUESTSTATUS") |
| queryParams.Set("requestid", asyncId) |
| err = CallCollectionsApi(ctx, cloud, queryParams, asyncStatus) |
| if _, apiErr := CheckForCollectionsApiError("REQUESTSTATUS", asyncStatus.ResponseHeader, asyncStatus.Error); apiErr != nil { |
| err = apiErr |
| } |
| if err == nil { |
| asyncState = asyncStatus.Status.AsyncState |
| message = asyncStatus.Status.Message |
| } |
| |
| return |
| } |
| |
| func DeleteAsyncRequest(ctx context.Context, cloud *solr.SolrCloud, asyncId string) (status string, err error) { |
| deleteStatus := &SolrDeleteRequestStatus{} |
| |
| queryParams := url.Values{} |
| queryParams.Set("action", "DELETESTATUS") |
| queryParams.Set("requestid", asyncId) |
| err = CallCollectionsApi(ctx, cloud, queryParams, deleteStatus) |
| if _, apiErr := CheckForCollectionsApiError("DELETESTATUS", deleteStatus.ResponseHeader, deleteStatus.Error); apiErr != nil { |
| err = apiErr |
| } |
| if err == nil { |
| status = deleteStatus.Status |
| } |
| |
| return |
| } |
| |
| func CallCollectionsApi(ctx context.Context, cloud *solr.SolrCloud, urlParams url.Values, response interface{}) (err error) { |
| cloudUrl := solr.InternalURLForCloud(cloud) |
| |
| client := noVerifyTLSHttpClient |
| if mTLSHttpClient != nil { |
| client = mTLSHttpClient |
| } |
| |
| urlParams.Set("wt", "json") |
| |
| cloudUrl = cloudUrl + "/solr/admin/collections?" + urlParams.Encode() |
| |
| resp := &http.Response{} |
| |
| req, err := http.NewRequest("GET", cloudUrl, nil) |
| |
| // Any custom HTTP headers passed through the Context |
| if httpHeaders, hasHeaders := ctx.Value(HTTP_HEADERS_CONTEXT_KEY).(map[string]string); hasHeaders { |
| for key, header := range httpHeaders { |
| req.Header.Add(key, header) |
| } |
| } |
| |
| if resp, err = client.Do(req); err != nil { |
| return err |
| } |
| |
| defer resp.Body.Close() |
| |
| if err == nil && resp.StatusCode >= 400 { |
| b, _ := io.ReadAll(resp.Body) |
| err = errors.NewServiceUnavailable(fmt.Sprintf("Recieved bad response code of %d from solr with response: %s", resp.StatusCode, string(b))) |
| } |
| |
| if err == nil { |
| err = json.NewDecoder(resp.Body).Decode(&response) |
| } |
| |
| return err |
| } |
| |
| func init() { |
| // setup an http client that can talk to Solr pods using untrusted, self-signed certs |
| customTransport := http.DefaultTransport.(*http.Transport).Clone() |
| customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} |
| SetNoVerifyTLSHttpClient(&http.Client{Transport: customTransport}) |
| } |