Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions historyserver/config/historyserver-azureblob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: v1
kind: Service
metadata:
name: historyserver
labels:
app: historyserver
spec:
selector:
app: historyserver
ports:
- protocol: TCP
name: http
port: 30080
targetPort: 8080
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: historyserver-demo
labels:
app: historyserver
spec:
replicas: 1
selector:
matchLabels:
app: historyserver
template:
metadata:
labels:
app: historyserver
spec:
serviceAccountName: historyserver
containers:
- name: historyserver
env:
- name: AZURE_STORAGE_CONNECTION_STRING
value: "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite-service.azurite-dev.svc.cluster.local:10000/devstoreaccount1;"
- name: AZURE_STORAGE_CONTAINER
value: "ray-historyserver"
image: historyserver:v0.1.0
imagePullPolicy: IfNotPresent
command:
- historyserver
- --runtime-class-name=azureblob
- --ray-root-dir=log
ports:
- containerPort: 8080
resources:
limits:
cpu: "500m"
3 changes: 2 additions & 1 deletion historyserver/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ Sample configs are in the `config/` directory:
| `raycluster.yaml` | Ray cluster with collector sidecar (S3/MinIO) |
| `raycluster-azureblob.yaml` | Ray cluster with collector sidecar (Azure Blob) |
| `rayjob.yaml` | Sample Ray job for testing |
| `historyserver.yaml` | History Server deployment |
| `historyserver.yaml` | History Server deployment (S3/MinIO) |
| `historyserver-azureblob.yaml` | History Server deployment (Azure Blob) |
| `service_account.yaml` | Service account for History Server |

## Additional resources
Expand Down
53 changes: 53 additions & 0 deletions historyserver/test/e2e/historyserver_azureblob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package e2e

import (
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"

. "github.com/ray-project/kuberay/historyserver/test/support"
. "github.com/ray-project/kuberay/ray-operator/test/support"
)

func TestAzureHistoryServer(t *testing.T) {
azureClient := EnsureAzureBlobClient(t)

tests := []struct {
name string
testFunc func(Test, *WithT, *corev1.Namespace, *azblob.Client)
}{
{
name: "Live cluster: historyserver endpoints should be accessible",
testFunc: testAzureLiveClusters,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
test := With(t)
g := NewWithT(t)
namespace := test.NewTestNamespace()

tt.testFunc(test, g, namespace, azureClient)
})
}
}

func testAzureLiveClusters(test Test, g *WithT, namespace *corev1.Namespace, azureClient *azblob.Client) {
rayCluster := PrepareAzureHistoryServerTestEnv(test, g, namespace, azureClient)
ApplyRayJobAndWaitForCompletion(test, g, namespace, rayCluster)
ApplyAzureHistoryServer(test, g, namespace)
historyServerURL := GetHistoryServerURL(test, g, namespace)

clusterInfo := getClusterFromList(test, g, historyServerURL, rayCluster.Name, namespace.Name)
g.Expect(clusterInfo.SessionName).To(Equal(LiveSessionName), "Live cluster should have sessionName='live'")

client := CreateHTTPClientWithCookieJar(g)
setClusterContext(test, g, client, historyServerURL, namespace.Name, rayCluster.Name, clusterInfo.SessionName)
verifyHistoryServerEndpoints(test, g, client, historyServerURL)

DeleteAzureBlobContainer(test, g, azureClient)
LogWithTimestamp(test.T(), "Azure live clusters E2E test completed successfully")
}
60 changes: 59 additions & 1 deletion historyserver/test/support/azureblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
Expand All @@ -24,7 +25,8 @@ const (
AzureContainerName = "ray-historyserver"

// Azure-specific RayCluster config
AzureRayClusterManifestPath = "../../config/raycluster-azureblob.yaml"
AzureRayClusterManifestPath = "../../config/raycluster-azureblob.yaml"
AzureHistoryServerManifestPath = "../../config/historyserver-azureblob.yaml"
)

// ApplyAzurite deploys Azurite once per test namespace.
Expand Down Expand Up @@ -154,3 +156,59 @@ func ApplyAzureRayClusterWithCollector(test Test, g *WithT, namespace *corev1.Na

return rayCluster
}

// ApplyAzureHistoryServer deploys the HistoryServer configured for Azure Blob Storage.
func ApplyAzureHistoryServer(test Test, g *WithT, namespace *corev1.Namespace) {
sa, clusterRole, clusterRoleBinding := DeserializeRBACFromYAML(test, ServiceAccountManifestPath)
sa.Namespace = namespace.Name
clusterRoleBinding.Name = fmt.Sprintf("historyserver-%s", namespace.Name)
clusterRoleBinding.Subjects[0].Namespace = namespace.Name

_, err := test.Client().Core().CoreV1().ServiceAccounts(namespace.Name).Create(test.Ctx(), sa, metav1.CreateOptions{})
g.Expect(err).NotTo(HaveOccurred())

_, err = test.Client().Core().RbacV1().ClusterRoles().Create(test.Ctx(), clusterRole, metav1.CreateOptions{})
if err != nil && !k8serrors.IsAlreadyExists(err) {
g.Expect(err).NotTo(HaveOccurred())
}

_, err = test.Client().Core().RbacV1().ClusterRoleBindings().Create(test.Ctx(), clusterRoleBinding, metav1.CreateOptions{})
g.Expect(err).NotTo(HaveOccurred())

test.T().Cleanup(func() {
_ = test.Client().Core().RbacV1().ClusterRoleBindings().Delete(
context.Background(), clusterRoleBinding.Name, metav1.DeleteOptions{})
})

KubectlApplyYAML(test, AzureHistoryServerManifestPath, namespace.Name)

LogWithTimestamp(test.T(), "Waiting for Azure HistoryServer to be ready")
g.Eventually(func(gg Gomega) {
pods, err := test.Client().Core().CoreV1().Pods(namespace.Name).List(
test.Ctx(), metav1.ListOptions{
LabelSelector: "app=historyserver",
},
)
gg.Expect(err).NotTo(HaveOccurred())
gg.Expect(pods.Items).NotTo(BeEmpty())
gg.Expect(AllPodsRunningAndReady(pods.Items)).To(BeTrue())
}, TestTimeoutMedium).Should(Succeed())
LogWithTimestamp(test.T(), "Azure HistoryServer is ready")
}

// PrepareAzureHistoryServerTestEnv prepares test environment for Azure History Server tests.
func PrepareAzureHistoryServerTestEnv(test Test, g *WithT, namespace *corev1.Namespace, azureClient *azblob.Client) *rayv1.RayCluster {
rayCluster := ApplyAzureRayClusterWithCollector(test, g, namespace)

headPod, err := GetHeadPod(test, rayCluster)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(headPod.Spec.Containers).To(ContainElement(
WithTransform(func(c corev1.Container) string { return c.Name }, Equal("collector")),
))

containerClient := azureClient.ServiceClient().NewContainerClient(AzureContainerName)
_, err = containerClient.GetProperties(context.Background(), nil)
g.Expect(err).NotTo(HaveOccurred())

return rayCluster
}
Loading