Deprecate the ServerMaintenance resource#1001
Conversation
Signed-off-by: Nagadeesh Nagaraja <nagdeesh@gmail.com>
9d6d8d7 to
249d53c
Compare
Signed-off-by: Nagadeesh Nagaraja <nagdeesh@gmail.com>
Signed-off-by: Nagadeesh Nagaraja <nagdeesh@gmail.com>
Signed-off-by: Nagadeesh Nagaraja <nagdeesh@gmail.com>
📝 WalkthroughWalkthrough
ChangesServerMaintenance deprecation and migration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ServerMaintenanceReconciler
participant Server
Client->>ServerMaintenanceReconciler: Create deprecated ServerMaintenance
ServerMaintenanceReconciler->>ServerMaintenanceReconciler: Reconcile API-group resource
ServerMaintenanceReconciler->>Client: Delete Pending resource
Client->>ServerMaintenanceReconciler: Delete InMaintenance resource
ServerMaintenanceReconciler->>Server: Clear maintenance reference and LED
ServerMaintenanceReconciler-->>Client: Complete finalizer cleanup
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
internal/controller/index_fields.go (1)
61-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
ServerMaintenanceGVK literal across files.The same
schema.GroupVersionKind{Group: "servermaintenance.metal.ironcore.dev", Version: "v1alpha1", Kind: "ServerMaintenance"}literal is hardcoded independently in both files (and per graph evidence, in internal/controller/suite_test.go too). Extracting a single exportedschema.GroupVersionKindvar avoids drift if the group/version/kind ever changes.
internal/controller/index_fields.go#L61-L76: define a package-levelvar ServerMaintenanceGVK = schema.GroupVersionKind{...}here and use it when buildingsmObj.cmd/main.go#L385-L412: import and reusecontroller.ServerMaintenanceGVKinstead of redefining the literal.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/controller/index_fields.go` around lines 61 - 76, Centralize the ServerMaintenance GVK to prevent duplicated literals: in internal/controller/index_fields.go#L61-L76, define the exported package-level ServerMaintenanceGVK and use it when configuring smObj; in cmd/main.go#L385-L412, import the controller package and reuse controller.ServerMaintenanceGVK instead of redefining the schema.GroupVersionKind literal.internal/controller/suite_test.go (1)
86-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCapture stderr from
go list -mfor better CI diagnostics.
cmd.Output()discards stderr; if module resolution fails (e.g., proxy/module-cache issues in CI), the wrapped error won't show why.♻️ Proposed fix to include stderr in the error
func maintenanceOperatorCRDDir() (string, error) { const modulePath = "github.com/ironcore-dev/metal-maintenance-operator" - out, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", modulePath).Output() - if err != nil { - return "", fmt.Errorf("failed to resolve module dir for %s: %w", modulePath, err) - } + cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", modulePath) + var stderr bytes.Buffer + cmd.Stderr = &stderr + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("failed to resolve module dir for %s: %w (stderr: %s)", modulePath, err, stderr.String()) + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/controller/suite_test.go` around lines 86 - 100, Update maintenanceOperatorCRDDir to capture stderr from the go list -m command, using the command execution API that returns combined output or otherwise preserves stderr. Include that captured diagnostic output in the wrapped error returned when module resolution fails.internal/controller/biossettings_controller_test.go (1)
4-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFive legacy controller test files disabled via
//go:build ignorewith no tracking reference. All share the same root cause: they're excluded fromgo build/go testbecause their controllers still depend on the now-external ServerMaintenance API (per suite_test.go's explanatory comment), but none carry a TODO/issue reference for re-enabling once those controllers migrate — risking silent bit-rot (undetected compile breakage) until someone remembers to lift the tag.
internal/controller/biossettings_controller_test.go#L4-L5: add a// TODO(#<issue>): re-enable once BIOSSettings migrates to servermaintenance.metal.ironcore.devnote next to the build tag.internal/controller/biossettingsset_controller_test.go#L4-L5: same tracking note for BIOSSettingsSet.internal/controller/biosversionset_controller_test.go#L4-L5: same tracking note for BIOSVersionSet.internal/controller/bmcsettings_controller_test.go#L4-L5: same tracking note for BMCSettings.internal/controller/bmcsettingsset_controller_test.go#L4-L5: same tracking note for BMCSettingsSet.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/controller/biossettings_controller_test.go` around lines 4 - 5, Add a TODO with an issue reference beside the //go:build ignore tag in internal/controller/biossettings_controller_test.go:4-5, internal/controller/biossettingsset_controller_test.go:4-5, internal/controller/biosversionset_controller_test.go:4-5, internal/controller/bmcsettings_controller_test.go:4-5, and internal/controller/bmcsettingsset_controller_test.go:4-5, identifying that each test should be re-enabled after its corresponding controller migrates to servermaintenance.metal.ironcore.dev.docs/concepts/servermaintenance.md (1)
3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeprecation notice lacks a concrete removal target.
"Will be removed in a future release" doesn't give consumers an actionable timeline. The PR's own objective calls for defining a removal timeline; consider naming a target version/date (or linking the tracking issue) so downstream consumers can plan migration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/concepts/servermaintenance.md` around lines 3 - 5, Update the deprecation notice near the ServerMaintenance API documentation to specify a concrete removal target, such as the planned release version or date, or link the relevant tracking issue. Preserve the existing replacement API and operator guidance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/main.go`:
- Around line 385-412: Make manager startup tolerate the ServerMaintenance CRD
being absent: update RegisterIndexFields and its startup call path to handle
RESTMapping/informer initialization errors for
servermaintenance.metal.ironcore.dev/v1alpha1 without exiting main, while still
registering the index when the CRD is available. Preserve normal controller
startup and ensure later availability can be handled through the existing
registration/retry mechanism if one exists.
In `@internal/webhook/v1alpha1/webhook_suite_test.go`:
- Around line 68-99: Update the ctrl.NewManager configuration in the webhook
test suite to mirror production’s unstructured ServerMaintenance cache settings:
configure Cache.Options.ByObject for the servermaintenance.metal.ironcore.dev
GVK and enable Client.Options.Cache.Unstructured. Keep RegisterIndexFields using
the same GVK so webhook unstructured List calls exercise the cache-backed
indexer.
---
Nitpick comments:
In `@docs/concepts/servermaintenance.md`:
- Around line 3-5: Update the deprecation notice near the ServerMaintenance API
documentation to specify a concrete removal target, such as the planned release
version or date, or link the relevant tracking issue. Preserve the existing
replacement API and operator guidance.
In `@internal/controller/biossettings_controller_test.go`:
- Around line 4-5: Add a TODO with an issue reference beside the //go:build
ignore tag in internal/controller/biossettings_controller_test.go:4-5,
internal/controller/biossettingsset_controller_test.go:4-5,
internal/controller/biosversionset_controller_test.go:4-5,
internal/controller/bmcsettings_controller_test.go:4-5, and
internal/controller/bmcsettingsset_controller_test.go:4-5, identifying that each
test should be re-enabled after its corresponding controller migrates to
servermaintenance.metal.ironcore.dev.
In `@internal/controller/index_fields.go`:
- Around line 61-76: Centralize the ServerMaintenance GVK to prevent duplicated
literals: in internal/controller/index_fields.go#L61-L76, define the exported
package-level ServerMaintenanceGVK and use it when configuring smObj; in
cmd/main.go#L385-L412, import the controller package and reuse
controller.ServerMaintenanceGVK instead of redefining the
schema.GroupVersionKind literal.
In `@internal/controller/suite_test.go`:
- Around line 86-100: Update maintenanceOperatorCRDDir to capture stderr from
the go list -m command, using the command execution API that returns combined
output or otherwise preserves stderr. Include that captured diagnostic output in
the wrapped error returned when module resolution fails.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 88193c72-75a7-4248-8b41-c22542994c0d
⛔ Files ignored due to path filters (2)
dist/chart/templates/crd/metal.ironcore.dev_servermaintenances.yamlis excluded by!**/dist/**go.sumis excluded by!**/*.sum
📒 Files selected for processing (20)
api/v1alpha1/servermaintenance_types.gocmd/main.goconfig/crd/bases/metal.ironcore.dev_servermaintenances.yamldocs/concepts/servermaintenance.mdgo.modinternal/controller/biossettings_controller_test.gointernal/controller/biossettingsset_controller_test.gointernal/controller/biosversion_controller_test.gointernal/controller/biosversionset_controller_test.gointernal/controller/bmcsettings_controller_test.gointernal/controller/bmcsettingsset_controller_test.gointernal/controller/bmcversion_controller_test.gointernal/controller/bmcversionset_controller_test.gointernal/controller/index_fields.gointernal/controller/server_controller.gointernal/controller/server_controller_test.gointernal/controller/servermaintenance_controller.gointernal/controller/servermaintenance_controller_test.gointernal/controller/suite_test.gointernal/webhook/v1alpha1/webhook_suite_test.go
| smU := &unstructured.Unstructured{} | ||
| smU.SetGroupVersionKind(schema.GroupVersionKind{ | ||
| Group: "servermaintenance.metal.ironcore.dev", | ||
| Version: "v1alpha1", | ||
| Kind: "ServerMaintenance", | ||
| }) | ||
| mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ | ||
| Scheme: scheme, | ||
| Metrics: metricsServerOptions, | ||
| WebhookServer: webhookServer, | ||
| HealthProbeBindAddress: probeAddr, | ||
| LeaderElection: enableLeaderElection, | ||
| LeaderElectionID: "f26702e4.ironcore.dev", | ||
| Cache: cache.Options{ | ||
| ByObject: map[client.Object]cache.ByObject{ | ||
| smU: {}, | ||
| }, | ||
| }, | ||
| Client: client.Options{ | ||
| Cache: &client.CacheOptions{ | ||
| // ServerMaintenance is only available as an unstructured type in this | ||
| // repo (its Go types now live in maintenance-operator). Unstructured | ||
| // Get/List calls bypass the cache by default, which would send field | ||
| // selectors (e.g. spec.serverRef.name) directly to the API server, | ||
| // which CRDs don't support. Enable caching so the field indexer is used. | ||
| Unstructured: true, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== cmd/main.go slice ==\n'
sed -n '360,450p' cmd/main.go
printf '\n== internal/controller/index_fields.go ==\n'
cat -n internal/controller/index_fields.go
printf '\n== references to ServerMaintenance ==\n'
rg -n "ServerMaintenance|servermaintenance\.metal\.ironcore\.dev|RegisterIndexFields|IndexField" -S .Repository: ironcore-dev/metal-operator
Length of output: 50384
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== internal/controller/suite_test.go (relevant slice) ==\n'
sed -n '70,210p' internal/controller/suite_test.go
printf '\n== internal/webhook/v1alpha1/webhook_suite_test.go (relevant slice) ==\n'
sed -n '60,170p' internal/webhook/v1alpha1/webhook_suite_test.go
printf '\n== docs / manifests mentioning maintenance-operator ==\n'
rg -n "maintenance-operator|servermaintenance\.metal\.ironcore\.dev|install.*CRD|CRD.*install" -S README.md docs config internal cmd api .github . || trueRepository: ironcore-dev/metal-operator
Length of output: 19874
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== go env =='
go env GOPATH GOMODCACHE GOPROXY | sed 's/^/ /'
echo '== controller-runtime module versions in go.mod =='
rg -n 'sigs.k8s.io/controller-runtime|k8s.io/apimachinery' go.mod
echo '== locate controller-runtime source in module cache =='
modcache="$(go env GOMODCACHE)"
fd 'indexer.go|cache.go|informer.go|delegating_client.go|field_index' "$modcache/sigs.k8s.io/controller-runtime*" -a | head -n 50
echo '== search for RESTMapping / GetInformer / IndexField in controller-runtime =='
rg -n "RESTMapping|GetInformer|IndexField|Unstructured" "$modcache/sigs.k8s.io/controller-runtime*" -g '*.go' | head -n 200Repository: ironcore-dev/metal-operator
Length of output: 586
🏁 Script executed:
#!/bin/bash
set -euo pipefail
modcache="$(go env GOMODCACHE)"
echo '== controller-runtime cache entries =='
find "$modcache/sigs.k8s.io" -maxdepth 2 -type d | sed -n '1,80p'
echo '== controller-runtime files of interest =='
find "$modcache/sigs.k8s.io" -path '*controller-runtime*' -type f \( \
-name '*.go' -o -name 'README*' -o -name '*.md' \
\) | rg 'cache|informer|field|client' | sed -n '1,120p'Repository: ironcore-dev/metal-operator
Length of output: 8605
🏁 Script executed:
#!/bin/bash
set -euo pipefail
modcache="$(go env GOMODCACHE)"
cr="$modcache/sigs.k8s.io/controller-runtime@v0.24.1"
printf '\n== pkg/cache/internal/informers.go ==\n'
cat -n "$cr/pkg/cache/internal/informers.go" | sed -n '1,260p'
printf '\n== pkg/cache/informer_cache.go ==\n'
cat -n "$cr/pkg/cache/informer_cache.go" | sed -n '1,260p'
printf '\n== pkg/client/apiutil/restmapper.go ==\n'
cat -n "$cr/pkg/client/apiutil/restmapper.go" | sed -n '1,260p'
printf '\n== search for IndexField implementation ==\n'
rg -n "func .*IndexField|IndexField\\(" "$cr/pkg" -g '*.go' | sed -n '1,120p'Repository: ironcore-dev/metal-operator
Length of output: 35173
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cr="$(go env GOMODCACHE)/sigs.k8s.io/controller-runtime@v0.24.1"
printf '\n== pkg/cache/internal/informers.go (Get and cache creation) ==\n'
rg -n "^func \\(ip \\*Informers\\) Get|^func \\(ip \\*Informers\\) get|newCache|RESTMapping|ResourceFor|mapToGVK|unstructured" "$cr/pkg/cache/internal/informers.go" -n
echo
sed -n '260,520p' "$cr/pkg/cache/internal/informers.go"
printf '\n== cache test around TestIndexFieldDoesNotBlock ==\n'
sed -n '2720,2795p' "$cr/pkg/cache/cache_test.go"Repository: ironcore-dev/metal-operator
Length of output: 10408
Manager startup now hard-depends on the external maintenance-operator CRD
RegisterIndexFields() eagerly creates the unstructured informer and calls RESTMapping for servermaintenance.metal.ironcore.dev/v1alpha1. If that CRD is not installed yet, manager setup fails and main() exits before any controller starts. Document the install order for maintenance-operator, or make the index registration tolerate a missing CRD instead of crashing startup.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/main.go` around lines 385 - 412, Make manager startup tolerate the
ServerMaintenance CRD being absent: update RegisterIndexFields and its startup
call path to handle RESTMapping/informer initialization errors for
servermaintenance.metal.ironcore.dev/v1alpha1 without exiting main, while still
registering the index when the CRD is available. Preserve normal controller
startup and ensure later availability can be handled through the existing
registration/retry mechanism if one exists.
| // maintenanceOperatorCRDDir returns the config/crd/bases directory of the | ||
| // github.com/ironcore-dev/metal-maintenance-operator module (e.g. for the | ||
| // ServerMaintenance CRD), resolved from the local Go module cache/proxy via | ||
| // the go.mod requirement rather than a relative path to a sibling checkout. | ||
| func maintenanceOperatorCRDDir() (string, error) { | ||
| const modulePath = "github.com/ironcore-dev/metal-maintenance-operator" | ||
|
|
||
| out, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", modulePath).Output() | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to resolve module dir for %s: %w", modulePath, err) | ||
| } | ||
|
|
||
| dir := strings.TrimSpace(string(out)) | ||
| if dir == "" { | ||
| return "", fmt.Errorf("empty module dir for %s", modulePath) | ||
| } | ||
|
|
||
| return filepath.Join(dir, "config", "crd", "bases"), nil | ||
| } | ||
|
|
||
| var _ = BeforeSuite(func() { | ||
| ctx, cancel = context.WithCancel(context.TODO()) | ||
|
|
||
| By("bootstrapping test environment") | ||
| maintenanceCRDDir, err := maintenanceOperatorCRDDir() | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| testEnv = &envtest.Environment{ | ||
| CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, | ||
| CRDDirectoryPaths: []string{ | ||
| filepath.Join("..", "..", "..", "config", "crd", "bases"), | ||
| maintenanceCRDDir, | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Manager here doesn't mirror the unstructured ServerMaintenance cache config added in cmd/main.go.
This block wires up loading the maintenance-operator CRDs into envtest, but the ctrl.NewManager call later in this file (~line 137) still lacks the Cache.Options.ByObject/Client.Options.Cache.Unstructured=true config that cmd/main.go and internal/controller/suite_test.go add for the same unstructured servermaintenance.metal.ironcore.dev GVK. RegisterIndexFields (Line 148) will still succeed at registering the indexer now that the CRD is loaded, but any unstructured List call against this GVK from webhook code would bypass the cache here (unlike production), since that flag controls whether unstructured Get/List go through the cache-backed field indexer or hit the API server directly with an unsupported field selector.
Run this to check whether any webhook code performs field-selector-based List calls on the unstructured ServerMaintenance type (which would surface this gap):
#!/bin/bash
rg -nP -C3 'serverRefField|unstructured\.Unstructured' internal/webhook -g '*.go'🔧 Suggested fix to mirror cmd/main.go's manager config
+ smU := &unstructured.Unstructured{}
+ smU.SetGroupVersionKind(k8sschema.GroupVersionKind{
+ Group: "servermaintenance.metal.ironcore.dev",
+ Version: "v1alpha1",
+ Kind: "ServerMaintenance",
+ })
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
WebhookServer: webhook.NewServer(webhook.Options{
Host: webhookInstallOptions.LocalServingHost,
Port: webhookInstallOptions.LocalServingPort,
CertDir: webhookInstallOptions.LocalServingCertDir,
}),
LeaderElection: false,
Metrics: metricsserver.Options{BindAddress: "0"},
+ Cache: cache.Options{
+ ByObject: map[client.Object]cache.ByObject{
+ smU: {},
+ },
+ },
+ Client: client.Options{
+ Cache: &client.CacheOptions{
+ Unstructured: true,
+ },
+ },
})🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/webhook/v1alpha1/webhook_suite_test.go` around lines 68 - 99, Update
the ctrl.NewManager configuration in the webhook test suite to mirror
production’s unstructured ServerMaintenance cache settings: configure
Cache.Options.ByObject for the servermaintenance.metal.ironcore.dev GVK and
enable Client.Options.Cache.Unstructured. Keep RegisterIndexFields using the
same GVK so webhook unstructured List calls exercise the cache-backed indexer.
|
I would suggest that in this PR we only mark the resource/types as deprecated and do the removal in a separate one. |
|
Can we please only do the deprecation of the resource in this PR similar to: #1019 The migration logic (deletion etc.) and a smooth transition will be the next PR. For version v0.8.0 we can then remove the types and controller code. |
| // +kubebuilder:object:root=true | ||
| // +kubebuilder:subresource:status | ||
| // +kubebuilder:resource:shortName=sm | ||
| // +kubebuilder:deprecatedversion:warning="metal.ironcore.dev/v1alpha1 ServerMaintenance is deprecated; use servermaintenance.metal.ironcore.dev/v1alpha1 instead" |
There was a problem hiding this comment.
I would leave out the alternative usage.
| smU := &unstructured.Unstructured{} | ||
| smU.SetGroupVersionKind(schema.GroupVersionKind{ | ||
| Group: "servermaintenance.metal.ironcore.dev", | ||
| Version: "v1alpha1", | ||
| Kind: "ServerMaintenance", | ||
| }) |
There was a problem hiding this comment.
That part should be not in this PR.
| Cache: cache.Options{ | ||
| ByObject: map[client.Object]cache.ByObject{ | ||
| smU: {}, | ||
| }, | ||
| }, | ||
| Client: client.Options{ | ||
| Cache: &client.CacheOptions{ | ||
| // ServerMaintenance is only available as an unstructured type in this | ||
| // repo (its Go types now live in maintenance-operator). Unstructured | ||
| // Get/List calls bypass the cache by default, which would send field | ||
| // selectors (e.g. spec.serverRef.name) directly to the API server, | ||
| // which CRDs don't support. Enable caching so the field indexer is used. | ||
| Unstructured: true, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
We should only do the deprecation announcement in this PR.
| //go:build ignore | ||
|
|
There was a problem hiding this comment.
We should still keep the current codeline + tests intact.
| smObj := &unstructured.Unstructured{} | ||
| smObj.SetGroupVersionKind(schema.GroupVersionKind{ | ||
| Group: "servermaintenance.metal.ironcore.dev", | ||
| Version: "v1alpha1", | ||
| Kind: "ServerMaintenance", | ||
| }) | ||
| if err := indexer.IndexField(ctx, smObj, serverRefField, func(rawObj client.Object) []string { | ||
| item, ok := rawObj.(*unstructured.Unstructured) |
There was a problem hiding this comment.
No functional changes on the indexing needed here.
|
Closing in favor of #1024 |
Fixes #
Summary by CodeRabbit
API Updates
metal.ironcore.dev/v1alpha1ServerMaintenance API.servermaintenance.metal.ironcore.dev/v1alpha1for new resources.Behavior Changes
Documentation
Bug Fixes