diff --git a/providers/mock/mock.go b/providers/mock/mock.go new file mode 100644 index 000000000..3b436185d --- /dev/null +++ b/providers/mock/mock.go @@ -0,0 +1,260 @@ +package mock + +import ( + "log" + "time" + + "fmt" + "github.com/virtual-kubelet/virtual-kubelet/providers" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// MockProvider implements the virtual-kubelet provider interface and stores pods in memory. +type MockProvider struct { + nodeName string + operatingSystem string + internalIP string + daemonEndpointPort int32 + pods map[string]*v1.Pod +} + +// NewMockProvider creates a new MockProvider +func NewMockProvider(nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockProvider, error) { + provider := MockProvider{ + nodeName: nodeName, + operatingSystem: operatingSystem, + internalIP: internalIP, + daemonEndpointPort: daemonEndpointPort, + pods: make(map[string]*v1.Pod), + } + + return &provider, nil +} + +// CreatePod accepts a Pod definition and stores it in memory. +func (p *MockProvider) CreatePod(pod *v1.Pod) error { + log.Printf("receive CreatePod %q\n", pod.Name) + + key, err := buildKey(pod) + if err != nil { + return err + } + + p.pods[key] = pod + + return nil +} + +// UpdatePod accepts a Pod definition and updates its reference. +func (p *MockProvider) UpdatePod(pod *v1.Pod) error { + log.Printf("receive UpdatePod %q\n", pod.Name) + + key, err := buildKey(pod) + if err != nil { + return err + } + + p.pods[key] = pod + + return nil +} + +// DeletePod deletes the specified pod out of memory. +func (p *MockProvider) DeletePod(pod *v1.Pod) (err error) { + log.Printf("receive DeletePod %q\n", pod.Name) + + key, err := buildKey(pod) + if err != nil { + return err + } + + delete(p.pods, key) + + return nil +} + +// GetPod returns a pod by name that is stored in memory. +func (p *MockProvider) GetPod(namespace, name string) (pod *v1.Pod, err error) { + log.Printf("receive GetPod %q\n", pod.Name) + + key, err := buildKey(pod) + if err != nil { + return nil, err + } + + if pod, ok := p.pods[key]; ok { + return pod, nil + } + + return nil, nil +} + +// GetContainerLogs retrieves the logs of a container by name from the provider. +func (p *MockProvider) GetContainerLogs(namespace, podName, containerName string, tail int) (string, error) { + log.Printf("receive GetContainerLogs %q\n", podName) + return "", nil +} + +// GetPodStatus returns the status of a pod by name that is "running". +// returns nil if a pod by that name is not found. +func (p *MockProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) { + log.Printf("receive GetPodStatus %q\n", name) + + now := metav1.NewTime(time.Now()) + + status := &v1.PodStatus{ + Phase: v1.PodRunning, + HostIP: "1.2.3.4", + PodIP: "5.6.7.8", + StartTime: &now, + Conditions: []v1.PodCondition{ + { + Type: v1.PodInitialized, + Status: v1.ConditionTrue, + }, + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + { + Type: v1.PodScheduled, + Status: v1.ConditionTrue, + }, + }, + } + + pod, err := p.GetPod(namespace, name) + if err != nil { + return status, err + } + + for _, container := range pod.Spec.Containers { + status.ContainerStatuses = append(status.ContainerStatuses, v1.ContainerStatus{ + Name: container.Name, + Image: container.Image, + Ready: true, + RestartCount: 0, + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: now, + }, + }, + }) + } + + return status, nil +} + +// GetPods returns a list of all pods known to be "running". +func (p *MockProvider) GetPods() ([]*v1.Pod, error) { + log.Printf("receive GetPods\n") + + var pods []*v1.Pod + + for _, pod := range p.pods { + pods = append(pods, pod) + } + + return pods, nil +} + +// Capacity returns a resource list containing the capacity limits. +func (p *MockProvider) Capacity() v1.ResourceList { + // TODO: These should be configurable + return v1.ResourceList{ + "cpu": resource.MustParse("20"), + "memory": resource.MustParse("100Gi"), + "pods": resource.MustParse("20"), + } +} + +// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status +// within Kubernetes. +func (p *MockProvider) NodeConditions() []v1.NodeCondition { + // TODO: Make this configurable + return []v1.NodeCondition{ + { + Type: "Ready", + Status: v1.ConditionTrue, + LastHeartbeatTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Reason: "KubeletReady", + Message: "kubelet is ready.", + }, + { + Type: "OutOfDisk", + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Reason: "KubeletHasSufficientDisk", + Message: "kubelet has sufficient disk space available", + }, + { + Type: "MemoryPressure", + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Reason: "KubeletHasSufficientMemory", + Message: "kubelet has sufficient memory available", + }, + { + Type: "DiskPressure", + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Reason: "KubeletHasNoDiskPressure", + Message: "kubelet has no disk pressure", + }, + { + Type: "NetworkUnavailable", + Status: v1.ConditionFalse, + LastHeartbeatTime: metav1.Now(), + LastTransitionTime: metav1.Now(), + Reason: "RouteCreated", + Message: "RouteController created a route", + }, + } + +} + +// NodeAddresses returns a list of addresses for the node status +// within Kubernetes. +func (p *MockProvider) NodeAddresses() []v1.NodeAddress { + return []v1.NodeAddress{ + { + Type: "InternalIP", + Address: p.internalIP, + }, + } +} + +// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status +// within Kubernetes. +func (p *MockProvider) NodeDaemonEndpoints() *v1.NodeDaemonEndpoints { + return &v1.NodeDaemonEndpoints{ + KubeletEndpoint: v1.DaemonEndpoint{ + Port: p.daemonEndpointPort, + }, + } +} + +// OperatingSystem returns the operating system for this provider. +// This is a noop to default to Linux for now. +func (p *MockProvider) OperatingSystem() string { + return providers.OperatingSystemLinux +} + +// buildKey is a helper for building the "key" for the providers pod store. +func buildKey(pod *v1.Pod) (string, error) { + if pod.ObjectMeta.Namespace == "" { + return "", fmt.Errorf("pod namespace not found") + } + + if pod.ObjectMeta.Name == "" { + return "", fmt.Errorf("pod name not found") + } + + return fmt.Sprintf("%s-%s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name), nil +} diff --git a/vkubelet/vkubelet.go b/vkubelet/vkubelet.go index e617e6814..3b99b8a99 100644 --- a/vkubelet/vkubelet.go +++ b/vkubelet/vkubelet.go @@ -13,6 +13,7 @@ import ( "github.com/virtual-kubelet/virtual-kubelet/manager" "github.com/virtual-kubelet/virtual-kubelet/providers/azure" "github.com/virtual-kubelet/virtual-kubelet/providers/hypersh" + "github.com/virtual-kubelet/virtual-kubelet/providers/mock" "github.com/virtual-kubelet/virtual-kubelet/providers/web" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -70,13 +71,11 @@ func New(nodeName, operatingSystem, namespace, kubeConfig, taint, provider, prov i64value, err := strconv.ParseInt(daemonEndpointPortEnv, 10, 32) daemonEndpointPort := int32(i64value) + internalIP := os.Getenv("VKUBELET_POD_IP") + var p Provider switch provider { case "azure": - internalIP := os.Getenv("VKUBELET_POD_IP") - if err != nil { - return nil, err - } p, err = azure.NewACIProvider(providerConfig, rm, nodeName, operatingSystem, internalIP, daemonEndpointPort) if err != nil { return nil, err @@ -91,6 +90,11 @@ func New(nodeName, operatingSystem, namespace, kubeConfig, taint, provider, prov if err != nil { return nil, err } + case "mock": + p, err = mock.NewMockProvider(nodeName, operatingSystem, internalIP, daemonEndpointPort) + if err != nil { + return nil, err + } default: fmt.Printf("Provider '%s' is not supported\n", provider) }