Merge pull request #1154 from r2d4/svc-tests
Refactor service package and add test coveragepull/1180/head
commit
b26f0aa66b
|
@ -68,12 +68,11 @@ var serviceCmd = &cobra.Command{
|
|||
defer api.Close()
|
||||
|
||||
cluster.EnsureMinikubeRunningOrExit(api, 1)
|
||||
if err := service.ValidateService(namespace, svc); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf("service '%s' could not be found running in namespace '%s' within kubernetes",
|
||||
svc, namespace))
|
||||
err = service.WaitAndMaybeOpenService(api, namespace, svc, serviceURLTemplate, serviceURLMode, https)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error opening service: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
service.WaitAndMaybeOpenService(api, namespace, svc, serviceURLTemplate, serviceURLMode, https)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,6 @@ import (
|
|||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/1.5/kubernetes"
|
||||
"k8s.io/client-go/1.5/tools/clientcmd"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/assets"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
|
@ -421,21 +419,6 @@ func CreateSSHShell(api libmachine.API, args []string) error {
|
|||
return client.Shell(strings.Join(args, " "))
|
||||
}
|
||||
|
||||
func GetKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
|
||||
}
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating new client from kubeConfig.ClientConfig()")
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// EnsureMinikubeRunningOrExit checks that minikube has a status available and that
|
||||
// that the status is `Running`, otherwise it will exit
|
||||
func EnsureMinikubeRunningOrExit(api libmachine.API, exitStatus int) {
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
corev1 "k8s.io/client-go/1.5/kubernetes/typed/core/v1"
|
||||
kubeapi "k8s.io/client-go/1.5/pkg/api"
|
||||
"k8s.io/client-go/1.5/pkg/api/v1"
|
||||
"k8s.io/client-go/1.5/tools/clientcmd"
|
||||
|
||||
"text/template"
|
||||
|
||||
|
@ -39,6 +40,33 @@ import (
|
|||
"k8s.io/minikube/pkg/util"
|
||||
)
|
||||
|
||||
type K8sClient interface {
|
||||
GetCoreClient() (corev1.CoreInterface, error)
|
||||
}
|
||||
|
||||
type K8sClientGetter struct{}
|
||||
|
||||
var k8s K8sClient
|
||||
|
||||
func init() {
|
||||
k8s = &K8sClientGetter{}
|
||||
}
|
||||
|
||||
func (*K8sClientGetter) GetCoreClient() (corev1.CoreInterface, error) {
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err := kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
|
||||
}
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating new client from kubeConfig.ClientConfig()")
|
||||
}
|
||||
return client.Core(), nil
|
||||
}
|
||||
|
||||
type ServiceURL struct {
|
||||
Namespace string
|
||||
Name string
|
||||
|
@ -47,6 +75,8 @@ type ServiceURL struct {
|
|||
|
||||
type ServiceURLs []ServiceURL
|
||||
|
||||
// Returns all the node port URLs for every service in a particular namespace
|
||||
// Accepts a template for formating
|
||||
func GetServiceURLs(api libmachine.API, namespace string, t *template.Template) (ServiceURLs, error) {
|
||||
host, err := cluster.CheckIfApiExistsAndLoad(api)
|
||||
if err != nil {
|
||||
|
@ -58,27 +88,22 @@ func GetServiceURLs(api libmachine.API, namespace string, t *template.Template)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
client, err := cluster.GetKubernetesClient()
|
||||
client, err := k8s.GetCoreClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getter := client.Services(namespace)
|
||||
serviceInterface := client.Services(namespace)
|
||||
|
||||
svcs, err := getter.List(kubeapi.ListOptions{})
|
||||
svcs, err := serviceInterface.List(kubeapi.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serviceURLs []ServiceURL
|
||||
|
||||
for _, svc := range svcs.Items {
|
||||
urls, err := getServiceURLsWithClient(client, ip, svc.Namespace, svc.Name, t)
|
||||
urls, err := printURLsForService(client, ip, svc.Name, svc.Namespace, t)
|
||||
if err != nil {
|
||||
if _, ok := err.(MissingNodePortError); ok {
|
||||
serviceURLs = append(serviceURLs, ServiceURL{Namespace: svc.Namespace, Name: svc.Name})
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
serviceURLs = append(serviceURLs, ServiceURL{Namespace: svc.Namespace, Name: svc.Name, URLs: urls})
|
||||
|
@ -87,123 +112,8 @@ func GetServiceURLs(api libmachine.API, namespace string, t *template.Template)
|
|||
return serviceURLs, nil
|
||||
}
|
||||
|
||||
// CheckService waits for the specified service to be ready by returning an error until the service is up
|
||||
// The check is done by polling the endpoint associated with the service and when the endpoint exists, returning no error->service-online
|
||||
func CheckService(namespace string, service string) error {
|
||||
client, err := cluster.GetKubernetesClient()
|
||||
if err != nil {
|
||||
return &util.RetriableError{Err: err}
|
||||
}
|
||||
endpoints := client.Endpoints(namespace)
|
||||
if err != nil {
|
||||
return &util.RetriableError{Err: err}
|
||||
}
|
||||
endpoint, err := endpoints.Get(service)
|
||||
if err != nil {
|
||||
return &util.RetriableError{Err: err}
|
||||
}
|
||||
return checkEndpointReady(endpoint)
|
||||
}
|
||||
|
||||
func checkEndpointReady(endpoint *v1.Endpoints) error {
|
||||
const notReadyMsg = "Waiting, endpoint for service is not ready yet...\n"
|
||||
if len(endpoint.Subsets) == 0 {
|
||||
fmt.Fprintf(os.Stderr, notReadyMsg)
|
||||
return &util.RetriableError{Err: errors.New("Endpoint for service is not ready yet")}
|
||||
}
|
||||
for _, subset := range endpoint.Subsets {
|
||||
if len(subset.Addresses) == 0 {
|
||||
fmt.Fprintf(os.Stderr, notReadyMsg)
|
||||
return &util.RetriableError{Err: errors.New("No endpoints for service are ready yet")}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WaitAndMaybeOpenService(api libmachine.API, namespace string, service string, urlTemplate *template.Template, urlMode bool, https bool) {
|
||||
if err := util.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s\n", service, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
urls, err := GetServiceURLsForService(api, namespace, service, urlTemplate)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, "Check that minikube is running and that you have specified the correct namespace (-n flag).")
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, url := range urls {
|
||||
if https {
|
||||
url = strings.Replace(url, "http", "https", 1)
|
||||
}
|
||||
if urlMode || !strings.HasPrefix(url, "http") {
|
||||
fmt.Fprintln(os.Stdout, url)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stdout, "Opening kubernetes service "+namespace+"/"+service+" in default browser...")
|
||||
browser.OpenURL(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetServiceListByLabel(namespace string, key string, value string) (*v1.ServiceList, error) {
|
||||
client, err := cluster.GetKubernetesClient()
|
||||
if err != nil {
|
||||
return &v1.ServiceList{}, &util.RetriableError{Err: err}
|
||||
}
|
||||
services := client.Services(namespace)
|
||||
if err != nil {
|
||||
return &v1.ServiceList{}, &util.RetriableError{Err: err}
|
||||
}
|
||||
return getServiceListFromServicesByLabel(services, key, value)
|
||||
}
|
||||
|
||||
func getServiceListFromServicesByLabel(services corev1.ServiceInterface, key string, value string) (*v1.ServiceList, error) {
|
||||
selector := labels.SelectorFromSet(labels.Set(map[string]string{key: value}))
|
||||
serviceList, err := services.List(kubeapi.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
return &v1.ServiceList{}, &util.RetriableError{Err: err}
|
||||
}
|
||||
|
||||
return serviceList, nil
|
||||
}
|
||||
|
||||
func getServicePortsFromServiceGetter(services serviceGetter, service string) ([]int32, error) {
|
||||
svc, err := services.Get(service)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting %s service: %s", service, err)
|
||||
}
|
||||
var nodePorts []int32
|
||||
if len(svc.Spec.Ports) > 0 {
|
||||
for _, port := range svc.Spec.Ports {
|
||||
if port.NodePort > 0 {
|
||||
nodePorts = append(nodePorts, port.NodePort)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(nodePorts) == 0 {
|
||||
return nil, MissingNodePortError{svc}
|
||||
}
|
||||
return nodePorts, nil
|
||||
}
|
||||
|
||||
type serviceGetter interface {
|
||||
Get(name string) (*v1.Service, error)
|
||||
List(kubeapi.ListOptions) (*v1.ServiceList, error)
|
||||
}
|
||||
|
||||
func getServicePorts(client *kubernetes.Clientset, namespace, service string) ([]int32, error) {
|
||||
services := client.Services(namespace)
|
||||
return getServicePortsFromServiceGetter(services, service)
|
||||
}
|
||||
|
||||
type MissingNodePortError struct {
|
||||
service *v1.Service
|
||||
}
|
||||
|
||||
func (e MissingNodePortError) Error() string {
|
||||
return fmt.Sprintf("Service %s/%s does not have a node port. To have one assigned automatically, the service type must be NodePort or LoadBalancer, but this service is of type %s.", e.service.Namespace, e.service.Name, e.service.Spec.Type)
|
||||
}
|
||||
|
||||
// Returns all the node ports for a service in a namespace
|
||||
// with optional formatting
|
||||
func GetServiceURLsForService(api libmachine.API, namespace, service string, t *template.Template) ([]string, error) {
|
||||
host, err := cluster.CheckIfApiExistsAndLoad(api)
|
||||
if err != nil {
|
||||
|
@ -215,26 +125,34 @@ func GetServiceURLsForService(api libmachine.API, namespace, service string, t *
|
|||
return nil, errors.Wrap(err, "Error getting ip from host")
|
||||
}
|
||||
|
||||
client, err := cluster.GetKubernetesClient()
|
||||
client, err := k8s.GetCoreClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getServiceURLsWithClient(client, ip, namespace, service, t)
|
||||
return printURLsForService(client, ip, service, namespace, t)
|
||||
}
|
||||
|
||||
func getServiceURLsWithClient(client *kubernetes.Clientset, ip, namespace, service string, t *template.Template) ([]string, error) {
|
||||
func printURLsForService(c corev1.CoreInterface, ip, service, namespace string, t *template.Template) ([]string, error) {
|
||||
if t == nil {
|
||||
return nil, errors.New("Error, attempted to generate service url with nil --format template")
|
||||
}
|
||||
|
||||
ports, err := getServicePorts(client, namespace, service)
|
||||
s := c.Services(namespace)
|
||||
svc, err := s.Get(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "service '%s' could not be found running", service)
|
||||
}
|
||||
var nodePorts []int32
|
||||
if len(svc.Spec.Ports) > 0 {
|
||||
for _, port := range svc.Spec.Ports {
|
||||
if port.NodePort > 0 {
|
||||
nodePorts = append(nodePorts, port.NodePort)
|
||||
}
|
||||
}
|
||||
}
|
||||
urls := []string{}
|
||||
for _, port := range ports {
|
||||
|
||||
for _, port := range nodePorts {
|
||||
var doc bytes.Buffer
|
||||
err = t.Execute(&doc, struct {
|
||||
IP string
|
||||
|
@ -257,14 +175,89 @@ func getServiceURLsWithClient(client *kubernetes.Clientset, ip, namespace, servi
|
|||
return urls, nil
|
||||
}
|
||||
|
||||
func ValidateService(namespace string, service string) error {
|
||||
client, err := cluster.GetKubernetesClient()
|
||||
// CheckService waits for the specified service to be ready by returning an error until the service is up
|
||||
// The check is done by polling the endpoint associated with the service and when the endpoint exists, returning no error->service-online
|
||||
func CheckService(namespace string, service string) error {
|
||||
client, err := k8s.GetCoreClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error validating input service name")
|
||||
return errors.Wrap(err, "Error getting kubernetes client")
|
||||
}
|
||||
services := client.Services(namespace)
|
||||
if _, err = services.Get(service); err != nil {
|
||||
return errors.Wrapf(err, "service '%s' could not be found running in namespace '%s' within kubernetes", service, namespace)
|
||||
err = validateService(services, service)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error validating service")
|
||||
}
|
||||
endpoints := client.Endpoints(namespace)
|
||||
return checkEndpointReady(endpoints, service)
|
||||
}
|
||||
|
||||
func validateService(s corev1.ServiceInterface, service string) error {
|
||||
if _, err := s.Get(service); err != nil {
|
||||
return errors.Wrapf(err, "Error getting service %s", service)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkEndpointReady(endpoints corev1.EndpointsInterface, service string) error {
|
||||
endpoint, err := endpoints.Get(service)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error getting endpoints for service %s", service)
|
||||
}
|
||||
const notReadyMsg = "Waiting, endpoint for service is not ready yet...\n"
|
||||
if len(endpoint.Subsets) == 0 {
|
||||
fmt.Fprintf(os.Stderr, notReadyMsg)
|
||||
return &util.RetriableError{Err: errors.New("Endpoint for service is not ready yet")}
|
||||
}
|
||||
for _, subset := range endpoint.Subsets {
|
||||
if len(subset.Addresses) == 0 {
|
||||
fmt.Fprintf(os.Stderr, notReadyMsg)
|
||||
return &util.RetriableError{Err: errors.New("No endpoints for service are ready yet")}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WaitAndMaybeOpenService(api libmachine.API, namespace string, service string, urlTemplate *template.Template, urlMode bool, https bool) error {
|
||||
if err := util.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil {
|
||||
return errors.Wrapf(err, "Could not find finalized endpoint being pointed to by %s", service)
|
||||
}
|
||||
|
||||
urls, err := GetServiceURLsForService(api, namespace, service, urlTemplate)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Check that minikube is running and that you have specified the correct namespace")
|
||||
}
|
||||
for _, url := range urls {
|
||||
if https {
|
||||
url = strings.Replace(url, "http", "https", 1)
|
||||
}
|
||||
if urlMode || !strings.HasPrefix(url, "http") {
|
||||
fmt.Fprintln(os.Stdout, url)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stdout, "Opening kubernetes service "+namespace+"/"+service+" in default browser...")
|
||||
browser.OpenURL(url)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetServiceListByLabel(namespace string, key string, value string) (*v1.ServiceList, error) {
|
||||
client, err := k8s.GetCoreClient()
|
||||
if err != nil {
|
||||
return &v1.ServiceList{}, &util.RetriableError{Err: err}
|
||||
}
|
||||
services := client.Services(namespace)
|
||||
if err != nil {
|
||||
return &v1.ServiceList{}, &util.RetriableError{Err: err}
|
||||
}
|
||||
return getServiceListFromServicesByLabel(services, key, value)
|
||||
}
|
||||
|
||||
func getServiceListFromServicesByLabel(services corev1.ServiceInterface, key string, value string) (*v1.ServiceList, error) {
|
||||
selector := labels.SelectorFromSet(labels.Set(map[string]string{key: value}))
|
||||
serviceList, err := services.List(kubeapi.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
return &v1.ServiceList{}, &util.RetriableError{Err: err}
|
||||
}
|
||||
|
||||
return serviceList, nil
|
||||
}
|
||||
|
|
|
@ -17,64 +17,187 @@ limitations under the License.
|
|||
package service
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/machine/libmachine"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/client-go/1.5/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/1.5/kubernetes/typed/core/v1/fake"
|
||||
"k8s.io/client-go/1.5/pkg/api"
|
||||
"k8s.io/client-go/1.5/pkg/api/unversioned"
|
||||
"k8s.io/client-go/1.5/pkg/api/v1"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/minikube/tests"
|
||||
)
|
||||
|
||||
func TestCheckEndpointReady(t *testing.T) {
|
||||
endpointNoSubsets := &v1.Endpoints{}
|
||||
if err := checkEndpointReady(endpointNoSubsets); err == nil {
|
||||
t.Fatalf("Endpoint had no subsets but checkEndpointReady did not return an error")
|
||||
}
|
||||
type MockClientGetter struct {
|
||||
servicesMap map[string]corev1.ServiceInterface
|
||||
}
|
||||
|
||||
endpointNotReady := &v1.Endpoints{
|
||||
func (m *MockClientGetter) GetCoreClient() (corev1.CoreInterface, error) {
|
||||
return &MockCoreClient{
|
||||
servicesMap: m.servicesMap,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MockCoreClient struct {
|
||||
fake.FakeCore
|
||||
servicesMap map[string]corev1.ServiceInterface
|
||||
}
|
||||
|
||||
var serviceNamespaces = map[string]corev1.ServiceInterface{
|
||||
"default": defaultNamespaceServiceInterface,
|
||||
}
|
||||
|
||||
var defaultNamespaceServiceInterface = &MockServiceInterface{
|
||||
ServiceList: &v1.ServiceList{
|
||||
Items: []v1.Service{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "mock-dashboard",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Ports: []v1.ServicePort{
|
||||
{NodePort: int32(1111)},
|
||||
{NodePort: int32(2222)},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "mock-dashboard-no-ports",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Ports: []v1.ServicePort{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (m *MockCoreClient) Endpoints(namespace string) corev1.EndpointsInterface {
|
||||
return &MockEndpointsInterface{}
|
||||
}
|
||||
|
||||
func (m *MockCoreClient) Services(namespace string) corev1.ServiceInterface {
|
||||
return m.servicesMap[namespace]
|
||||
}
|
||||
|
||||
type MockEndpointsInterface struct {
|
||||
fake.FakeEndpoints
|
||||
Endpoints *v1.Endpoints
|
||||
}
|
||||
|
||||
var endpointMap = map[string]*v1.Endpoints{
|
||||
"no-subsets": {},
|
||||
"not-ready": {
|
||||
Subsets: []v1.EndpointSubset{
|
||||
{Addresses: []v1.EndpointAddress{},
|
||||
{
|
||||
Addresses: []v1.EndpointAddress{},
|
||||
NotReadyAddresses: []v1.EndpointAddress{
|
||||
{IP: "1.1.1.1"},
|
||||
{IP: "2.2.2.2"},
|
||||
{IP: "3.3.3.3"},
|
||||
}}}}
|
||||
if err := checkEndpointReady(endpointNotReady); err == nil {
|
||||
t.Fatalf("Endpoint had no Addresses but checkEndpointReady did not return an error")
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"one-ready": {
|
||||
Subsets: []v1.EndpointSubset{
|
||||
{
|
||||
Addresses: []v1.EndpointAddress{
|
||||
{IP: "1.1.1.1"},
|
||||
},
|
||||
NotReadyAddresses: []v1.EndpointAddress{
|
||||
{IP: "2.2.2.2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (e MockEndpointsInterface) Get(name string) (*v1.Endpoints, error) {
|
||||
endpoint, ok := endpointMap[name]
|
||||
if !ok {
|
||||
return nil, errors.New("Endpoint not found")
|
||||
}
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
func TestCheckEndpointReady(t *testing.T) {
|
||||
var tests = []struct {
|
||||
description string
|
||||
service string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
description: "Endpoint with no subsets should return an error",
|
||||
service: "no-subsets",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
description: "Endpoint with no ready endpoints should return an error",
|
||||
service: "not-ready",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
description: "Endpoint with at least one ready endpoint should not return an error",
|
||||
service: "one-ready",
|
||||
err: false,
|
||||
},
|
||||
}
|
||||
|
||||
endpointReady := &v1.Endpoints{
|
||||
Subsets: []v1.EndpointSubset{
|
||||
{Addresses: []v1.EndpointAddress{
|
||||
{IP: "1.1.1.1"},
|
||||
{IP: "2.2.2.2"},
|
||||
},
|
||||
NotReadyAddresses: []v1.EndpointAddress{},
|
||||
}},
|
||||
}
|
||||
if err := checkEndpointReady(endpointReady); err != nil {
|
||||
t.Fatalf("Endpoint was ready with at least one Address, but checkEndpointReady returned an error")
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := checkEndpointReady(&MockEndpointsInterface{}, test.service)
|
||||
if err != nil && !test.err {
|
||||
t.Errorf("Check endpoints returned an error: %+v", err)
|
||||
}
|
||||
if err == nil && test.err {
|
||||
t.Errorf("Check endpoints should have returned an error but returned nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type ServiceInterfaceMock struct {
|
||||
type MockServiceInterface struct {
|
||||
fake.FakeServices
|
||||
ServiceList *v1.ServiceList
|
||||
}
|
||||
|
||||
func (s ServiceInterfaceMock) List(opts api.ListOptions) (*v1.ServiceList, error) {
|
||||
func (s MockServiceInterface) List(opts api.ListOptions) (*v1.ServiceList, error) {
|
||||
serviceList := &v1.ServiceList{
|
||||
Items: []v1.Service{},
|
||||
}
|
||||
keyValArr := strings.Split(opts.LabelSelector.String(), "=")
|
||||
for _, service := range s.ServiceList.Items {
|
||||
if service.Spec.Selector[keyValArr[0]] == keyValArr[1] {
|
||||
serviceList.Items = append(serviceList.Items, service)
|
||||
if opts.LabelSelector != nil {
|
||||
keyValArr := strings.Split(opts.LabelSelector.String(), "=")
|
||||
|
||||
for _, service := range s.ServiceList.Items {
|
||||
if service.Spec.Selector[keyValArr[0]] == keyValArr[1] {
|
||||
serviceList.Items = append(serviceList.Items, service)
|
||||
}
|
||||
}
|
||||
|
||||
return serviceList, nil
|
||||
}
|
||||
|
||||
return s.ServiceList, nil
|
||||
}
|
||||
|
||||
func (s MockServiceInterface) Get(name string) (*v1.Service, error) {
|
||||
for _, svc := range s.ServiceList.Items {
|
||||
if svc.ObjectMeta.Name == name {
|
||||
return &svc, nil
|
||||
}
|
||||
}
|
||||
return serviceList, nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestGetServiceListFromServicesByLabel(t *testing.T) {
|
||||
|
@ -89,7 +212,7 @@ func TestGetServiceListFromServicesByLabel(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
serviceIface := ServiceInterfaceMock{
|
||||
serviceIface := MockServiceInterface{
|
||||
ServiceList: serviceList,
|
||||
}
|
||||
if _, err := getServiceListFromServicesByLabel(&serviceIface, "nothing", "nothing"); err != nil {
|
||||
|
@ -101,69 +224,186 @@ func TestGetServiceListFromServicesByLabel(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type MockServiceGetter struct {
|
||||
services map[string]v1.Service
|
||||
}
|
||||
|
||||
func NewMockServiceGetter() *MockServiceGetter {
|
||||
return &MockServiceGetter{
|
||||
services: make(map[string]v1.Service),
|
||||
func TestPrintURLsForService(t *testing.T) {
|
||||
defaultTemplate := template.Must(template.New("svc-template").Parse("{{.IP}}:{{.Port}}"))
|
||||
client := &MockCoreClient{
|
||||
servicesMap: serviceNamespaces,
|
||||
}
|
||||
}
|
||||
|
||||
func (mockServiceGetter *MockServiceGetter) Get(name string) (*v1.Service, error) {
|
||||
service, ok := mockServiceGetter.services[name]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Error getting %s service from mockServiceGetter", name)
|
||||
var tests = []struct {
|
||||
description string
|
||||
serviceName string
|
||||
namespace string
|
||||
tmpl *template.Template
|
||||
expectedOutput []string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
description: "should get all node ports",
|
||||
serviceName: "mock-dashboard",
|
||||
namespace: "default",
|
||||
tmpl: defaultTemplate,
|
||||
expectedOutput: []string{"127.0.0.1:1111", "127.0.0.1:2222"},
|
||||
},
|
||||
{
|
||||
description: "empty slice for no node ports",
|
||||
serviceName: "mock-dashboard-no-ports",
|
||||
namespace: "default",
|
||||
tmpl: defaultTemplate,
|
||||
expectedOutput: []string{},
|
||||
},
|
||||
{
|
||||
description: "throw error without template",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
return &service, nil
|
||||
}
|
||||
|
||||
func (mockServiceGetter *MockServiceGetter) List(options api.ListOptions) (*v1.ServiceList, error) {
|
||||
services := v1.ServiceList{
|
||||
TypeMeta: unversioned.TypeMeta{Kind: "ServiceList", APIVersion: "v1"},
|
||||
ListMeta: unversioned.ListMeta{},
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
urls, err := printURLsForService(client, "127.0.0.1", test.serviceName, test.namespace, test.tmpl)
|
||||
if err != nil && !test.err {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
if err == nil && test.err {
|
||||
t.Errorf("Expected error but got none")
|
||||
}
|
||||
if !reflect.DeepEqual(urls, test.expectedOutput) {
|
||||
t.Errorf("Expected %+v \n\n Actual: %+v \n\n")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, svc := range mockServiceGetter.services {
|
||||
services.Items = append(services.Items, svc)
|
||||
}
|
||||
return &services, nil
|
||||
}
|
||||
|
||||
func TestGetServiceURLs(t *testing.T) {
|
||||
mockServiceGetter := NewMockServiceGetter()
|
||||
expected := []int32{1111, 2222}
|
||||
mockDashboardService := v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
Ports: []v1.ServicePort{
|
||||
{
|
||||
NodePort: expected[0],
|
||||
}, {
|
||||
NodePort: expected[1],
|
||||
}},
|
||||
defaultAPI := &tests.MockAPI{
|
||||
Hosts: map[string]*host.Host{
|
||||
constants.MachineName: {
|
||||
Name: constants.MachineName,
|
||||
Driver: &tests.MockDriver{},
|
||||
},
|
||||
},
|
||||
}
|
||||
mockServiceGetter.services["mock-service"] = mockDashboardService
|
||||
defaultTemplate := template.Must(template.New("svc-template").Parse("{{.IP}}:{{.Port}}"))
|
||||
|
||||
ports, err := getServicePortsFromServiceGetter(mockServiceGetter, "mock-service")
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting mock-service ports from api: Error: %s", err)
|
||||
var tests = []struct {
|
||||
description string
|
||||
api libmachine.API
|
||||
namespace string
|
||||
expected ServiceURLs
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
description: "no host",
|
||||
api: &tests.MockAPI{
|
||||
Hosts: make(map[string]*host.Host),
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
description: "correctly return serviceURLs",
|
||||
namespace: "default",
|
||||
api: defaultAPI,
|
||||
expected: []ServiceURL{
|
||||
{
|
||||
Namespace: "default",
|
||||
Name: "mock-dashboard",
|
||||
URLs: []string{"127.0.0.1:1111", "127.0.0.1:2222"},
|
||||
},
|
||||
{
|
||||
Namespace: "default",
|
||||
Name: "mock-dashboard-no-ports",
|
||||
URLs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range ports {
|
||||
if ports[i] != expected[i] {
|
||||
t.Fatalf("Error getting mock-service port from api: Expected: %d, Got: %d", ports[0], expected)
|
||||
}
|
||||
|
||||
defer revertK8sClient(k8s)
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
k8s = &MockClientGetter{
|
||||
servicesMap: serviceNamespaces,
|
||||
}
|
||||
urls, err := GetServiceURLs(test.api, test.namespace, defaultTemplate)
|
||||
if err != nil && !test.err {
|
||||
t.Errorf("Error GetServiceURLs %s", err)
|
||||
}
|
||||
if err == nil && test.err {
|
||||
t.Errorf("Test should have failed, but didn't")
|
||||
}
|
||||
if !reflect.DeepEqual(urls, test.expected) {
|
||||
t.Errorf("URLs did not match, expected %+v \n\n got %+v", test.expected, urls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServiceURLWithoutNodePort(t *testing.T) {
|
||||
mockServiceGetter := NewMockServiceGetter()
|
||||
mockDashboardService := v1.Service{}
|
||||
mockServiceGetter.services["mock-service"] = mockDashboardService
|
||||
func TestGetServiceURLsForService(t *testing.T) {
|
||||
defaultAPI := &tests.MockAPI{
|
||||
Hosts: map[string]*host.Host{
|
||||
constants.MachineName: {
|
||||
Name: constants.MachineName,
|
||||
Driver: &tests.MockDriver{},
|
||||
},
|
||||
},
|
||||
}
|
||||
defaultTemplate := template.Must(template.New("svc-template").Parse("{{.IP}}:{{.Port}}"))
|
||||
|
||||
_, err := getServicePortsFromServiceGetter(mockServiceGetter, "mock-service")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error getting service with no node port")
|
||||
var tests = []struct {
|
||||
description string
|
||||
api libmachine.API
|
||||
namespace string
|
||||
service string
|
||||
expected []string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
description: "no host",
|
||||
api: &tests.MockAPI{
|
||||
Hosts: make(map[string]*host.Host),
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
description: "correctly return serviceURLs",
|
||||
namespace: "default",
|
||||
service: "mock-dashboard",
|
||||
api: defaultAPI,
|
||||
expected: []string{"127.0.0.1:1111", "127.0.0.1:2222"},
|
||||
},
|
||||
{
|
||||
description: "correctly return empty serviceURLs",
|
||||
namespace: "default",
|
||||
service: "mock-dashboard-no-ports",
|
||||
api: defaultAPI,
|
||||
expected: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
defer revertK8sClient(k8s)
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
k8s = &MockClientGetter{
|
||||
servicesMap: serviceNamespaces,
|
||||
}
|
||||
urls, err := GetServiceURLsForService(test.api, test.namespace, test.service, defaultTemplate)
|
||||
if err != nil && !test.err {
|
||||
t.Errorf("Error GetServiceURLsForService %s", err)
|
||||
}
|
||||
if err == nil && test.err {
|
||||
t.Errorf("Test should have failed, but didn't")
|
||||
}
|
||||
if !reflect.DeepEqual(urls, test.expected) {
|
||||
t.Errorf("URLs did not match, expected %+v \n\n got %+v", test.expected, urls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func revertK8sClient(k K8sClient) {
|
||||
k8s = k
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue