Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions controllers/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ func getCommonRbacRules() []rbacv1.PolicyRule {
Resources: []string{"pods"},
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"create", "get", "list", "delete"},
},
}
}

Expand Down
3 changes: 3 additions & 0 deletions controllers/ironic_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,9 @@ func (r *IronicReconciler) conductorDeploymentCreateOrUpdate(
TransportURLSecret: instance.Status.TransportURLSecret,
KeystoneEndpoints: *keystoneEndpoints,
TLS: instance.Spec.IronicAPI.TLS.Ca,
GraphicalConsoles: instance.Spec.GraphicalConsoles,
ConsoleImage: instance.Spec.Images.GraphicalConsole,
NoVNCProxyImage: instance.Spec.Images.NoVNCProxy,
}

if IronicConductorSpec.NodeSelector == nil {
Expand Down
48 changes: 48 additions & 0 deletions controllers/ironicconductor_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,48 @@ func (r *IronicConductorReconciler) reconcileServices(
}
}
}
if instance.Spec.GraphicalConsoles == "Enabled" {
//
// Create the conductor pod route to enable traffic to the
// novnc service, which graphical consoles are enabled
//
conductorRouteLabels := map[string]string{
common.AppSelector: ironic.ServiceName,
common.ComponentSelector: ironic.NoVNCComponent,
ironic.ConductorGroupSelector: ironicv1.ConductorGroupNull,
}
if instance.Spec.ConductorGroup != "" {
conductorRouteLabels[ironic.ConductorGroupSelector] = strings.ToLower(instance.Spec.ConductorGroup)
}

novncRoute := ironicconductor.RouteNoVNC(conductorPod.Name, instance, conductorRouteLabels)
err = controllerutil.SetOwnerReference(&conductorPod, novncRoute, helper.GetScheme())
if err != nil {
return ctrl.Result{}, err
}
err = r.Get(
ctx,
types.NamespacedName{
Name: novncRoute.Name,
Namespace: novncRoute.Namespace,
},
novncRoute,
)
if err != nil && k8s_errors.IsNotFound(err) {
Log.Info(fmt.Sprintf("Route %s does not exist, creating it", novncRoute.Name))
err = r.Create(ctx, novncRoute)
if err != nil {
return ctrl.Result{}, err
}
} else {
Log.Info(fmt.Sprintf("Route %s exists, updating it", novncRoute.Name))
err = r.Update(ctx, novncRoute)
if err != nil {
return ctrl.Result{}, err
}
}

}
}

Log.Info("Reconciled Conductor Services successfully")
Expand Down Expand Up @@ -922,6 +964,11 @@ func (r *IronicConductorReconciler) generateServiceConfigMaps(
templateParameters["Standalone"] = instance.Spec.Standalone
templateParameters["ConductorGroup"] = instance.Spec.ConductorGroup
templateParameters["LogPath"] = ironicconductor.LogPath
graphicalConsolesEnabled := instance.Spec.GraphicalConsoles == "Enabled"
templateParameters["GraphicalConsolesEnabled"] = graphicalConsolesEnabled
if graphicalConsolesEnabled {
templateParameters["ConsoleImage"] = instance.Spec.ConsoleImage
}

databaseAccount := db.GetAccount()
dbSecret := db.GetSecret()
Expand Down Expand Up @@ -960,6 +1007,7 @@ func (r *IronicConductorReconciler) generateServiceConfigMaps(
AdditionalTemplate: map[string]string{
"ironic.conf": "/common/config/ironic.conf",
"01-conductor.conf": "/ironicconductor/config/01-conductor.conf",
"01-novnc.conf": "/ironicconductor/config/01-novnc.conf",
"03-init-container-conductor.conf": "/ironicconductor/config/03-init-container-conductor.conf",
"dnsmasq.conf": "/common/config/dnsmasq.conf",
},
Expand Down
2 changes: 2 additions & 0 deletions pkg/ironic/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
APIComponent = "api"
// InspectorComponent -
InspectorComponent = "inspector"
// NoVNCComponent -
NoVNCComponent = "novnc"
// ConductorGroupSelector -
ConductorGroupSelector = "conductorGroup"
// ImageDirectory -
Expand Down
2 changes: 2 additions & 0 deletions pkg/ironic/initcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type APIDetails struct {
PxeInit bool
ConductorInit bool
DeployHTTPURL string
NoVNCProxyURL string
IngressDomain string
ProvisionNetwork string
ImageDirectory string
Expand All @@ -57,6 +58,7 @@ func InitContainer(init APIDetails) []corev1.Container {
envVars["DatabaseHost"] = env.SetValue(init.DatabaseHost)
envVars["DatabaseName"] = env.SetValue(init.DatabaseName)
envVars["DeployHTTPURL"] = env.SetValue(init.DeployHTTPURL)
envVars["NoVNCProxyURL"] = env.SetValue(init.NoVNCProxyURL)
envVars["IngressDomain"] = env.SetValue(init.IngressDomain)

envs := []corev1.EnvVar{
Expand Down
37 changes: 37 additions & 0 deletions pkg/ironicconductor/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ func Service(
ports = append(ports, httpbootPort)
}

// Expose the ironic-novncproxy HTTP port if graphical consoles is enabled
if instance.Spec.GraphicalConsoles == "Enabled" {
novncPort := corev1.ServicePort{
Name: ironic.NoVNCComponent,
Port: 6090,
Protocol: corev1.ProtocolTCP,
}
ports = append(ports, novncPort)

}

if len(ports) == 0 {
return nil
}
Expand Down Expand Up @@ -80,3 +91,29 @@ func Route(
},
}
}

// RouteNoVNC - Route for novnc service when graphical consoles are enabled
func RouteNoVNC(
serviceName string,
instance *ironicv1.IronicConductor,
routeLabels map[string]string,
) *routev1.Route {
serviceRef := routev1.RouteTargetReference{
Kind: "Service",
Name: serviceName,
}
routePort := &routev1.RoutePort{
TargetPort: intstr.FromString(ironic.NoVNCComponent),
}
return &routev1.Route{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName + "-novnc",
Namespace: instance.Namespace,
Labels: routeLabels,
},
Spec: routev1.RouteSpec{
To: serviceRef,
Port: routePort,
},
}
}
57 changes: 53 additions & 4 deletions pkg/ironicconductor/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ func StatefulSet(
PeriodSeconds: 30,
InitialDelaySeconds: 5,
}
novncLivenessProbe := &corev1.Probe{
TimeoutSeconds: 10,
PeriodSeconds: 30,
InitialDelaySeconds: 5,
}
novncReadinessProbe := &corev1.Probe{
TimeoutSeconds: 10,
PeriodSeconds: 30,
InitialDelaySeconds: 5,
}

args := []string{"-c", ServiceCommand}

Expand Down Expand Up @@ -115,6 +125,12 @@ func StatefulSet(
httpbootReadinessProbe.TCPSocket = &corev1.TCPSocketAction{
Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(8088)},
}
novncLivenessProbe.TCPSocket = &corev1.TCPSocketAction{
Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(6090)},
}
novncReadinessProbe.TCPSocket = &corev1.TCPSocketAction{
Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(6090)},
}

// Parse the storageRequest defined in the CR
storageRequest, err := resource.ParseQuantity(instance.Spec.StorageRequest)
Expand Down Expand Up @@ -156,13 +172,18 @@ func StatefulSet(
httpbootEnvVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS")
httpbootEnvVars["CONFIG_HASH"] = env.SetValue(configHash)

novncEnvVars := map[string]env.Setter{}
novncEnvVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS")
novncEnvVars["CONFIG_HASH"] = env.SetValue(configHash)

ramdiskLogsEnvVars := map[string]env.Setter{}
ramdiskLogsEnvVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS")
ramdiskLogsEnvVars["CONFIG_HASH"] = env.SetValue(configHash)

volumes := GetVolumes(ctx, instance)
conductorVolumeMounts := GetVolumeMounts("ironic-conductor")
httpbootVolumeMounts := GetVolumeMounts("httpboot")
novncVolumeMounts := GetVolumeMounts("novnc")
dnsmasqVolumeMounts := GetVolumeMounts("dnsmasq")
ramdiskLogsVolumeMounts := GetVolumeMounts("ramdisk-logs")
initVolumeMounts := GetInitVolumeMounts(instance)
Expand All @@ -175,6 +196,7 @@ func StatefulSet(
dnsmasqVolumeMounts = append(dnsmasqVolumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...)
ramdiskLogsVolumeMounts = append(ramdiskLogsVolumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...)
initVolumeMounts = append(initVolumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...)
novncVolumeMounts = append(novncVolumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...)
}

resourceName := fmt.Sprintf("%s-%s", ironic.ServiceName, ironic.ConductorComponent)
Expand Down Expand Up @@ -269,11 +291,29 @@ func StatefulSet(
LivenessProbe: dnsmasqLivenessProbe,
// StartupProbe: startupProbe,
}
containers = []corev1.Container{
conductorContainer,
httpbootContainer,
dnsmasqContainer,
containers = append(containers, dnsmasqContainer)
}

if instance.Spec.GraphicalConsoles == "Enabled" {
// Only include the novnc container if graphical consoles are enabled
novncContainer := corev1.Container{
Name: "novnc",
Command: []string{
"/bin/bash",
},
Args: args,
Image: instance.Spec.NoVNCProxyImage,
SecurityContext: &corev1.SecurityContext{
RunAsUser: &runAsUser,
},
Env: env.MergeEnvs([]corev1.EnvVar{}, novncEnvVars),
VolumeMounts: novncVolumeMounts,
Resources: instance.Spec.Resources,
ReadinessProbe: novncReadinessProbe,
LivenessProbe: novncLivenessProbe,
// StartupProbe: startupProbe,
}
containers = append(containers, novncContainer)
}

// Default oslo.service graceful_shutdown_timeout is 60, so align with that
Expand Down Expand Up @@ -346,6 +386,14 @@ func StatefulSet(
// Build what the fully qualified Route hostname will be when the Route exists
deployHTTPURL = "http://%(PodName)s-%(PodNamespace)s.%(IngressDomain)s/"
}
novncProxyURL := ""
if instance.Spec.GraphicalConsoles == "Enabled" {

novncProtocol := "http"
// TODO(stevebaker) detect if https should be used, and also for deployHTTPURL above
novncDomain := "%(PodName)s-novnc-%(PodNamespace)s.%(IngressDomain)s"
novncProxyURL = fmt.Sprintf("%s://%s/vnc_auto.html", novncProtocol, novncDomain)
}

initContainerDetails := ironic.APIDetails{
ContainerImage: instance.Spec.ContainerImage,
Expand All @@ -362,6 +410,7 @@ func StatefulSet(
ConductorInit: true,
Privileged: true,
DeployHTTPURL: deployHTTPURL,
NoVNCProxyURL: novncProxyURL,
IngressDomain: ingressDomain,
ProvisionNetwork: instance.Spec.ProvisionNetwork,
}
Expand Down
2 changes: 0 additions & 2 deletions templates/common/config/ironic.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
enabled_hardware_types=ipmi,idrac,irmc,fake-hardware,redfish,manual-management,ilo,ilo5
enabled_bios_interfaces=no-bios,redfish,idrac-redfish,irmc,ilo
enabled_boot_interfaces=ipxe,ilo-ipxe,pxe,ilo-pxe,fake,redfish-virtual-media,idrac-redfish-virtual-media,ilo-virtual-media
enabled_console_interfaces=ipmitool-socat,ilo,no-console,fake
enabled_deploy_interfaces=direct,fake,ramdisk,custom-agent
default_deploy_interface=direct
enabled_inspect_interfaces=inspector,no-inspect,irmc,fake,redfish,ilo
Expand Down Expand Up @@ -36,7 +35,6 @@ auth_strategy={{if .Standalone}}noauth{{else}}keystone{{end}}
grub_config_path=EFI/BOOT/grub.cfg
isolinux_bin=/usr/share/syslinux/isolinux.bin


[agent]
deploy_logs_local_path=/var/lib/ironic/ramdisk-logs

Expand Down
9 changes: 9 additions & 0 deletions templates/ironicconductor/bin/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,13 @@ if [ ! -d "/var/lib/ironic/ramdisk-logs" ]; then
mkdir /var/lib/ironic/ramdisk-logs
fi

NOVNC_PROXY_URL=$(python3 -c '
import os
url_template = os.environ.get("NoVNCProxyURL", "")
if url_template:
print(url_template % os.environ)
')
crudini --set ${INIT_CONFIG} vnc public_url ${NOVNC_PROXY_URL}

echo "Conductor init successfully completed"
10 changes: 9 additions & 1 deletion templates/ironicconductor/config/01-conductor.conf
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
[DEFAULT]
# Default conductor configuration
enabled_console_interfaces={{if .GraphicalConsolesEnabled}}redfish-graphical,fake-graphical,{{end}}ipmitool-socat,ilo,no-console,fake

[conductor]
heartbeat_interval=20
heartbeat_timeout=120
allow_provisioning_in_maintenance=false
{{ if .ConductorGroup }}conductor_group={{ .ConductorGroup }}{{ end }}

{{if .GraphicalConsolesEnabled}}
[vnc]
enabled=True
container_provider=kubernetes
console_image={{ .ConsoleImage }}
read_only=false
{{end}}
2 changes: 2 additions & 0 deletions templates/ironicconductor/config/01-novnc.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[vnc]
enabled=True
36 changes: 36 additions & 0 deletions templates/ironicconductor/config/novnc-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"command": "/usr/bin/ironic-novncproxy --config-file /etc/ironic/ironic.conf --config-dir /etc/ironic/ironic.conf.d",
"config_files": [
{
"source": "/var/lib/config-data/default/ironic.conf",
"dest": "/etc/ironic/ironic.conf",
"owner": "ironic",
"perm": "0600"
},
{
"source": "/var/lib/config-data/default/01-novnc.conf",
"dest": "/etc/ironic/ironic.conf.d/01-novnc.conf",
"owner": "ironic",
"perm": "0600"
},
{
"source": "/var/lib/config-data/custom/02-ironic-custom.conf",
"dest": "/etc/ironic/ironic.conf.d/02-ironic-custom.conf",
"owner": "ironic",
"perm": "0600"
},
{
"source": "/var/lib/config-data/default/my.cnf",
"dest": "/etc/my.cnf",
"owner": "ironic",
"perm": "0644"
}
],
"permissions": [
{
"path": "/var/lib/ironic",
"owner": "ironic:ironic",
"recurse": true
}
]
}
3 changes: 2 additions & 1 deletion tests/functional/ironicconductor_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ var _ = Describe("IronicConductor controller", func() {
corev1.ConditionTrue,
)
role := th.GetRole(ironicNames.ConductorRole)
Expect(role.Rules).To(HaveLen(2))
Expect(role.Rules).To(HaveLen(3))
Expect(role.Rules[0].Resources).To(Equal([]string{"securitycontextconstraints"}))
Expect(role.Rules[1].Resources).To(Equal([]string{"pods"}))
Expect(role.Rules[2].Resources).To(Equal([]string{"secrets"}))
th.ExpectCondition(
ironicNames.ConductorName,
ConditionGetterFunc(IronicConductorConditionGetter),
Expand Down