Skip to content

Commit

Permalink
Merge branch 'main' into ConformanceTestFix-1
Browse files Browse the repository at this point in the history
  • Loading branch information
kale-amruta committed Jan 25, 2025
2 parents 0ad0fe1 + 2cd6f1f commit 6e2b055
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 40 deletions.
1 change: 1 addition & 0 deletions .backportrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"repoOwner": "loft-sh",
"repoName": "vcluster",
"targetBranchChoices": ["v0.19", "v0.20"],
"prDescription": "Backport from `{{sourceBranch}}` to `{{targetBranch}}`\n\nOriginal PR Nr.: #{{sourcePullRequest.number}}\n\n### Backported Commits:\n{{#each commits}}\n- {{shortSha this.sourceCommit.sha}} {{this.sourceCommit.message}}\n{{/each}}\n\n## Original PR Description:\n{{sourcePullRequest}}",
"branchLabelMapping": {
"^backport-to-(.+)$": "$1"
}
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/backport.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,34 @@ jobs:
if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport'))
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install GH CLI
uses: dev-hanz-ops/[email protected]

- name: Fetch PR description and update backportrc
env:
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
# Grab raw original PR body, gh CLI uses the default token.
BODY_CONTENT="$(gh api "/repos/$REPO/pulls/$PR_NUMBER" | jq -r .body)"
# Escape the markdown content so that sed can produce valid JSON.
# Remove starting and ending quites and replace new lines with literals.
ESCAPED_CONTENT=$(echo "$BODY_CONTENT" | jq -aRs .)
ESCAPED_CONTENT="${ESCAPED_CONTENT%\"}"
ESCAPED_CONTENT="${ESCAPED_CONTENT#\"}"
ESCAPED_CONTENT=$(echo "$ESCAPED_CONTENT" | sed 's/[\/&]/\\&/g')
# sourcePullRequest is an actual variable, but the PR description is not provided by the action or the CLI.
# Instead we are using it as a substitution target and replacing it with the markdown content.
sed -i "s/{{sourcePullRequest}}/$ESCAPED_CONTENT/g" .backportrc.json
- name: Backport Action
uses: sorenlouv/[email protected]
with:
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,24 @@ jobs:
});
console.log(response);
- name: Trigger CAPVC workflow
uses: actions/github-script@v7
continue-on-error: true
with:
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
script: |
const version = '${{ steps.get_version.outputs.release_version }}';
const response = await github.rest.actions.createWorkflowDispatch({
owner: 'loft-sh',
repo: 'cluster-api-provider-vcluster',
workflow_id: 'bump-vcluster.yaml',
ref: 'main',
inputs: {
version: version
}
});
console.log(response);
publish-chart:
if: startsWith(github.ref, 'refs/tags/v') == true
needs: [publish]
Expand Down
4 changes: 4 additions & 0 deletions chart/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3245,6 +3245,10 @@
"type": "boolean",
"description": "UseSecretsForSATokens will use secrets to save the generated service account tokens by virtual cluster instead of using a\npod annotation."
},
"runtimeClassName": {
"type": "string",
"description": "RuntimeClassName is the runtime class to set for synced pods."
},
"rewriteHosts": {
"$ref": "#/$defs/SyncRewriteHosts",
"description": "RewriteHosts is a special option needed to rewrite statefulset containers to allow the correct FQDN. virtual cluster will add\na small container to each stateful set pod that will initially rewrite the /etc/hosts file to match the FQDN expected by\nthe virtual cluster."
Expand Down
2 changes: 2 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ sync:
# UseSecretsForSATokens will use secrets to save the generated service account tokens by virtual cluster instead of using a
# pod annotation.
useSecretsForSATokens: false
# RuntimeClassName is the runtime class to set for synced pods.
runtimeClassName: ""
# RewriteHosts is a special option needed to rewrite statefulset containers to allow the correct FQDN. virtual cluster will add
# a small container to each stateful set pod that will initially rewrite the /etc/hosts file to match the FQDN expected by
# the virtual cluster.
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,9 @@ type SyncPods struct {
// pod annotation.
UseSecretsForSATokens bool `json:"useSecretsForSATokens,omitempty"`

// RuntimeClassName is the runtime class to set for synced pods.
RuntimeClassName string `json:"runtimeClassName,omitempty"`

// RewriteHosts is a special option needed to rewrite statefulset containers to allow the correct FQDN. virtual cluster will add
// a small container to each stateful set pod that will initially rewrite the /etc/hosts file to match the FQDN expected by
// the virtual cluster.
Expand Down
1 change: 1 addition & 0 deletions config/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ sync:
translateImage: {}
enforceTolerations: []
useSecretsForSATokens: false
runtimeClassName: ""
rewriteHosts:
enabled: true
initContainer:
Expand Down
7 changes: 7 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ module.exports = {
links: [],
copyright: `Copyright © ${new Date().getFullYear()} <a href="https://loft.sh/">Loft Labs, Inc.</a>`,
},
announcementBar: {
id: 'version-info',
content: '<strong>You are viewing docs for <code>vCluster</code> v0.19. <a href="https://www.vcluster.com/docs" target="_blank">See latest releases.</a></strong>',
backgroundColor: '#3c7a89',
textColor: '#ffffff',
isCloseable: true,
},
},
presets: [
[
Expand Down
57 changes: 54 additions & 3 deletions pkg/controllers/resources/pods/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"reflect"
"strings"
"time"

"github.com/loft-sh/vcluster/pkg/controllers/resources/pods/token"
Expand Down Expand Up @@ -94,6 +95,7 @@ func New(ctx *synccontext.RegisterContext) (syncertypes.Object, error) {

serviceName: ctx.Config.WorkloadService,
enableScheduler: ctx.Config.ControlPlane.Advanced.VirtualScheduler.Enabled,
fakeKubeletIPs: ctx.Config.Networking.Advanced.ProxyKubelets.ByIP,

virtualClusterClient: virtualClusterClient,
physicalClusterClient: physicalClusterClient,
Expand All @@ -112,6 +114,7 @@ type podSyncer struct {

serviceName string
enableScheduler bool
fakeKubeletIPs bool

podTranslator translatepods.Translator
virtualClusterClient kubernetes.Interface
Expand Down Expand Up @@ -224,9 +227,21 @@ func (s *podSyncer) SyncToHost(ctx *synccontext.SyncContext, event *synccontext.
}
}

// if scheduler is enabled we only sync if the pod has a node name
if s.enableScheduler && pPod.Spec.NodeName == "" {
return ctrl.Result{}, nil
if s.enableScheduler {
// if scheduler is enabled we only sync if the pod has a node name
if pPod.Spec.NodeName == "" {
return ctrl.Result{}, nil
}

if s.fakeKubeletIPs {
nodeIP, err := s.getNodeIP(ctx, pPod.Spec.NodeName)
if err != nil {
return ctrl.Result{}, err
}

pPod.Annotations[translatepods.HostIPAnnotation] = nodeIP
pPod.Annotations[translatepods.HostIPsAnnotation] = nodeIP
}
}

err = pro.ApplyPatchesHostObject(ctx, nil, pPod, event.Virtual, ctx.Config.Sync.ToHost.Pods.Patches, false)
Expand All @@ -238,6 +253,9 @@ func (s *podSyncer) SyncToHost(ctx *synccontext.SyncContext, event *synccontext.
}

func (s *podSyncer) Sync(ctx *synccontext.SyncContext, event *synccontext.SyncEvent[*corev1.Pod]) (_ ctrl.Result, retErr error) {
var (
err error
)
// should pod get deleted?
if event.Host.DeletionTimestamp != nil {
if event.Virtual.DeletionTimestamp == nil {
Expand Down Expand Up @@ -298,6 +316,13 @@ func (s *podSyncer) Sync(ctx *synccontext.SyncContext, event *synccontext.SyncEv
return patcher.DeleteVirtualObjectWithOptions(ctx, event.Virtual, event.Host, "node name is different between the two", &client.DeleteOptions{GracePeriodSeconds: &minimumGracePeriodInSeconds})
}

if s.fakeKubeletIPs && event.Host.Status.HostIP != "" {
err = s.rewriteFakeHostIPAddresses(ctx, event.Host)
if err != nil {
return ctrl.Result{}, err
}
}

// validate virtual pod before syncing it to the host cluster
if s.podSecurityStandard != "" {
valid, err := s.isPodSecurityStandardsValid(ctx, event.Virtual, ctx.Log)
Expand Down Expand Up @@ -468,3 +493,29 @@ func (s *podSyncer) assignNodeToPod(ctx *synccontext.SyncContext, pObj *corev1.P

return nil
}

func (s *podSyncer) rewriteFakeHostIPAddresses(ctx *synccontext.SyncContext, pPod *corev1.Pod) error {
nodeIP, err := s.getNodeIP(ctx, pPod.Spec.NodeName)
if err != nil {
return err
}

pPod.Status.HostIP = nodeIP
pPod.Status.HostIPs = []corev1.HostIP{
{IP: nodeIP},
}

return nil
}

func (s *podSyncer) getNodeIP(ctx *synccontext.SyncContext, name string) (string, error) {
serviceName := translate.SafeConcatName(translate.VClusterName, "node", strings.ReplaceAll(name, ".", "-"))

nodeService := &corev1.Service{}
err := ctx.CurrentNamespaceClient.Get(ctx.Context, types.NamespacedName{Name: serviceName, Namespace: ctx.CurrentNamespace}, nodeService)
if err != nil && !kerrors.IsNotFound(err) {
return "", fmt.Errorf("list services: %w", err)
}

return nodeService.Spec.ClusterIP, nil
}
44 changes: 44 additions & 0 deletions pkg/controllers/resources/pods/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,35 @@ func TestSync(t *testing.T) {
},
}

testNodeName := "test123"
pVclusterNodeService := pVclusterService.DeepCopy()
pVclusterNodeService.Name = translate.SafeConcatName(testingutil.DefaultTestVClusterName, "node", testNodeName)

pPodFakeKubelet := pPodBase.DeepCopy()
pPodFakeKubelet.Spec.NodeName = testNodeName
pPodFakeKubelet.Status.HostIP = "3.3.3.3"
pPodFakeKubelet.Status.HostIPs = []corev1.HostIP{
{IP: "3.3.3.3"},
}

vPodWithNodeName := &corev1.Pod{
ObjectMeta: vObjectMeta,
Spec: corev1.PodSpec{
NodeName: testNodeName,
},
}
vPodWithHostIP := vPodWithNodeName.DeepCopy()
vPodWithHostIP.Status.HostIP = pVclusterService.Spec.ClusterIP
vPodWithHostIP.Status.HostIPs = []corev1.HostIP{
{IP: pVclusterService.Spec.ClusterIP},
}

testNode := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: testNodeName,
},
}

syncertesting.RunTests(t, []*syncertesting.SyncTest{
{
Name: "Map hostpaths",
Expand All @@ -605,5 +634,20 @@ func TestSync(t *testing.T) {
assert.NilError(t, err)
},
},
{
Name: "Fake Kubelet enabled with Node sync",
InitialVirtualState: []runtime.Object{testNode.DeepCopy(), vPodWithNodeName, vNamespace.DeepCopy()},
InitialPhysicalState: []runtime.Object{testNode.DeepCopy(), pVclusterNodeService.DeepCopy(), pPodFakeKubelet.DeepCopy()},
ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{
corev1.SchemeGroupVersion.WithKind("Pod"): {vPodWithHostIP},
},
Sync: func(ctx *synccontext.RegisterContext) {
ctx.Config.Sync.FromHost.Nodes.Selector.All = true
ctx.Config.Networking.Advanced.ProxyKubelets.ByIP = true
syncContext, syncer := syncertesting.FakeStartSyncer(t, ctx, New)
_, err := syncer.(*podSyncer).Sync(syncContext, synccontext.NewSyncEventWithOld(pPodFakeKubelet, pPodFakeKubelet, vPodWithNodeName, vPodWithNodeName))
assert.NilError(t, err)
},
},
})
}
30 changes: 24 additions & 6 deletions pkg/controllers/resources/pods/translate/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
ClusterAutoScalerDaemonSetAnnotation = "cluster-autoscaler.kubernetes.io/daemonset-pod"
ServiceAccountNameAnnotation = "vcluster.loft.sh/service-account-name"
ServiceAccountTokenAnnotation = "vcluster.loft.sh/token-"
HostIPAnnotation = "vcluster.loft.sh/host-ip"
HostIPsAnnotation = "vcluster.loft.sh/host-ips"
)

var (
Expand Down Expand Up @@ -103,6 +105,7 @@ func NewTranslator(ctx *synccontext.RegisterContext, eventRecorder record.EventR
serviceAccountsEnabled: ctx.Config.Sync.ToHost.ServiceAccounts.Enabled,
priorityClassesEnabled: ctx.Config.Sync.ToHost.PriorityClasses.Enabled,
enableScheduler: ctx.Config.ControlPlane.Advanced.VirtualScheduler.Enabled,
fakeKubeletIPs: ctx.Config.Networking.Advanced.ProxyKubelets.ByIP,

mountPhysicalHostPaths: ctx.Config.ControlPlane.HostPathMapper.Enabled && !ctx.Config.ControlPlane.HostPathMapper.Central,

Expand Down Expand Up @@ -134,6 +137,7 @@ type translator struct {
overrideHostsResources corev1.ResourceRequirements
priorityClassesEnabled bool
enableScheduler bool
fakeKubeletIPs bool

virtualLogsPath string
virtualPodLogsPath string
Expand Down Expand Up @@ -325,6 +329,11 @@ func (t *translator) Translate(ctx *synccontext.SyncContext, vPod *corev1.Pod, s
return nil, err
}

// add runtime class name
if ctx.Config.Sync.ToHost.Pods.RuntimeClassName != "" {
pPod.Spec.RuntimeClassName = &ctx.Config.Sync.ToHost.Pods.RuntimeClassName
}

// translate topology spread constraints
if t.enableScheduler {
pPod.Spec.TopologySpreadConstraints = nil
Expand Down Expand Up @@ -405,7 +414,7 @@ func (t *translator) translateVolumes(ctx *synccontext.SyncContext, pPod *corev1
}
if pPod.Spec.Volumes[i].DownwardAPI != nil {
for j := range pPod.Spec.Volumes[i].DownwardAPI.Items {
translateFieldRef(pPod.Spec.Volumes[i].DownwardAPI.Items[j].FieldRef)
translateFieldRef(pPod.Spec.Volumes[i].DownwardAPI.Items[j].FieldRef, t.fakeKubeletIPs, t.enableScheduler)
}
}
if pPod.Spec.Volumes[i].ISCSI != nil && pPod.Spec.Volumes[i].ISCSI.SecretRef != nil {
Expand Down Expand Up @@ -471,7 +480,7 @@ func (t *translator) translateProjectedVolume(
}
if projectedVolume.Sources[i].DownwardAPI != nil {
for j := range projectedVolume.Sources[i].DownwardAPI.Items {
translateFieldRef(projectedVolume.Sources[i].DownwardAPI.Items[j].FieldRef)
translateFieldRef(projectedVolume.Sources[i].DownwardAPI.Items[j].FieldRef, t.fakeKubeletIPs, t.enableScheduler)
}
}
if projectedVolume.Sources[i].ServiceAccountToken != nil {
Expand Down Expand Up @@ -570,7 +579,7 @@ func (t *translator) translateProjectedVolume(
return nil
}

func translateFieldRef(fieldSelector *corev1.ObjectFieldSelector) {
func translateFieldRef(fieldSelector *corev1.ObjectFieldSelector, fakeKubeletIPs, enableScheduler bool) {
if fieldSelector == nil {
return
}
Expand All @@ -593,13 +602,22 @@ func translateFieldRef(fieldSelector *corev1.ObjectFieldSelector) {
fieldSelector.FieldPath = "metadata.annotations['" + UIDAnnotation + "']"
case "spec.serviceAccountName":
fieldSelector.FieldPath = "metadata.annotations['" + ServiceAccountNameAnnotation + "']"
// translate downward API references for status.hostIP(s) only when both virtual scheduler & fakeKubeletIPs are enabled
case "status.hostIP":
if fakeKubeletIPs && enableScheduler {
fieldSelector.FieldPath = "metadata.annotations['" + HostIPAnnotation + "']"
}
case "status.hostIPs":
if fakeKubeletIPs && enableScheduler {
fieldSelector.FieldPath = "metadata.annotations['" + HostIPsAnnotation + "']"
}
}
}

func (t *translator) TranslateContainerEnv(ctx *synccontext.SyncContext, envVar []corev1.EnvVar, envFrom []corev1.EnvFromSource, vPod *corev1.Pod, serviceEnvMap map[string]string) ([]corev1.EnvVar, []corev1.EnvFromSource, error) {
envNameMap := make(map[string]struct{})
for j, env := range envVar {
translateDownwardAPI(&envVar[j])
translateDownwardAPI(&envVar[j], t.fakeKubeletIPs, t.enableScheduler)
if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil && env.ValueFrom.ConfigMapKeyRef.Name != "" {
envVar[j].ValueFrom.ConfigMapKeyRef.Name = mappings.VirtualToHostName(ctx, envVar[j].ValueFrom.ConfigMapKeyRef.Name, vPod.Namespace, mappings.ConfigMaps())
}
Expand Down Expand Up @@ -640,14 +658,14 @@ func (t *translator) TranslateContainerEnv(ctx *synccontext.SyncContext, envVar
return envVar, envFrom, nil
}

func translateDownwardAPI(env *corev1.EnvVar) {
func translateDownwardAPI(env *corev1.EnvVar, fakeKubeletIPs, enableScheduler bool) {
if env.ValueFrom == nil {
return
}
if env.ValueFrom.FieldRef == nil {
return
}
translateFieldRef(env.ValueFrom.FieldRef)
translateFieldRef(env.ValueFrom.FieldRef, fakeKubeletIPs, enableScheduler)
}

func (t *translator) translateDNSConfig(pPod *corev1.Pod, vPod *corev1.Pod, nameServer string) {
Expand Down
Loading

0 comments on commit 6e2b055

Please sign in to comment.