Skip to content

Commit

Permalink
feat: add Apache Cloudstack support
Browse files Browse the repository at this point in the history
Add support for new platform.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
Signed-off-by: Claus Albøge <ca@netic.dk>
  • Loading branch information
Claus Albøge authored and smira committed Aug 27, 2024
1 parent 951cf66 commit 75cecb4
Show file tree
Hide file tree
Showing 11 changed files with 510 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ image-%: ## Builds the specified image. Valid options are aws, azure, digital-oc

images-essential: image-aws image-azure image-gcp image-metal secureboot-installer ## Builds only essential images used in the CI (AWS, GCP, and Metal).

images: image-akamai image-aws image-azure image-digital-ocean image-exoscale image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, OpenNebula, OpenStack, Oracle, Scaleway, UpCloud, Vultr and VMware).
images: image-akamai image-aws image-azure image-digital-ocean image-exoscale image-cloudstack image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, Cloudstack, GCP, HCloud, Metal, NoCloud, OpenNebula, OpenStack, Oracle, Scaleway, UpCloud, Vultr and VMware).

.PHONY: iso
iso: image-iso ## Builds the ISO and outputs it to the artifact directory.
Expand Down
6 changes: 6 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ The Talos bundled Flannel manifest was simplified to remove the `install-cni` st
title = "Device Extra Settle Timeout"
description = """\
Talos Linux now supports a kernel command line argument `talos.device.settle_time=3m` to set the device extra settle timeout to workaround issues with broken drivers.
"""

[notes.platform]
title = "Platform Support"
description = """\
Talos Linux now supports Apache CloudStack platform.
"""

[make_deps]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package cloudstack contains the Cloudstack platform implementation.
package cloudstack

import (
"context"
"fmt"
"log"
"net/netip"
"strings"

"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/go-procfs/procfs"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/netutils"
"github.com/siderolabs/talos/pkg/download"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)

// Cloudstack is the concrete type that implements the runtime.Platform interface.
type Cloudstack struct{}

// ParseMetadata converts Cloudstack platform metadata into platform network config.
func (e *Cloudstack) ParseMetadata(metadata *MetadataConfig) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}

if metadata.Hostname != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}

if err := hostnameSpec.ParseFQDN(metadata.Hostname); err != nil {
return nil, err
}

networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}

if metadata.PublicIPv4 != "" {
if ip, err := netip.ParseAddr(metadata.PublicIPv4); err == nil {
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
}

networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: e.Name(),
Hostname: metadata.Hostname,
Region: metadata.Zone,
Zone: metadata.Zone,
InstanceType: strings.ToLower(strings.SplitN(metadata.InstanceType, " ", 2)[0]),
InstanceID: metadata.InstanceID,
ProviderID: fmt.Sprintf("cloudstack://%s", metadata.InstanceID),
}

return networkConfig, nil
}

// Name implements the runtime.Platform interface.
func (e *Cloudstack) Name() string {
return "cloudstack"
}

// Configuration implements the runtime.Platform interface.
func (e *Cloudstack) Configuration(ctx context.Context, r state.State) ([]byte, error) {
if err := netutils.Wait(ctx, r); err != nil {
return nil, err
}

log.Printf("fetching machine config from %q", CloudstackUserDataEndpoint)

return download.Download(ctx, CloudstackUserDataEndpoint,
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
}

// Mode implements the runtime.Platform interface.
func (e *Cloudstack) Mode() runtime.Mode {
return runtime.ModeCloud
}

// KernelArgs implements the runtime.Platform interface.
func (e *Cloudstack) KernelArgs(string) procfs.Parameters {
return []*procfs.Parameter{
procfs.NewParameter("console").Append("tty1").Append("ttyS0"),
procfs.NewParameter(constants.KernelParamNetIfnames).Append("0"),
}
}

// NetworkConfiguration implements the runtime.Platform interface.
func (e *Cloudstack) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
log.Printf("fetching cloudstack instance config from: %q", CloudstackMetadataEndpoint)

metadata, err := e.getMetadata(ctx)
if err != nil {
return err
}

networkConfig, err := e.ParseMetadata(metadata)
if err != nil {
return err
}

select {
case ch <- networkConfig:
case <-ctx.Done():
return ctx.Err()
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package cloudstack_test

import (
_ "embed"
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/cloudstack"
)

//go:embed testdata/metadata.json
var rawMetadata []byte

//go:embed testdata/expected.yaml
var expectedNetworkConfig string

func TestEmpty(t *testing.T) {
p := &cloudstack.Cloudstack{}

var m cloudstack.MetadataConfig

require.NoError(t, json.Unmarshal(rawMetadata, &m))

networkConfig, err := p.ParseMetadata(&m)
require.NoError(t, err)

marshaled, err := yaml.Marshal(networkConfig)
require.NoError(t, err)

assert.Equal(t, expectedNetworkConfig, string(marshaled))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package cloudstack

import (
"context"
stderrors "errors"
"fmt"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/siderolabs/talos/pkg/download"
)

const (
// CloudstackMetadataEndpoint is the local Cloudstack endpoint.
CloudstackMetadataEndpoint = "http://data-server./latest/meta-data"
// CloudstackUserDataEndpoint is the local Cloudstack endpoint for the config.
CloudstackUserDataEndpoint = "http://data-server./latest/user-data"
)

// MetadataConfig represents a metadata Cloudstack instance.
type MetadataConfig struct {
Hostname string `json:"local-hostname,omitempty"`
InstanceID string `json:"instance-id,omitempty"`
InstanceType string `json:"service-offering,omitempty"`
PublicIPv4 string `json:"public-ipv4,omitempty"`
Zone string `json:"availability-zone,omitempty"`
}

/*
local-ipv4
public-hostname
vm-id
public-keys
cloud-identifier
hypervisor-host-name
*/

func (e *Cloudstack) getMetadata(ctx context.Context) (metadata *MetadataConfig, err error) {
getMetadataKey := func(key string) (string, error) {
res, metaerr := download.Download(ctx, fmt.Sprintf("%s/%s", CloudstackMetadataEndpoint, key),
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
if metaerr != nil && !stderrors.Is(metaerr, errors.ErrNoConfigSource) {
return "", fmt.Errorf("failed to fetch %q from IMDS: %w", key, metaerr)
}

return string(res), nil
}

metadata = &MetadataConfig{}

if metadata.Hostname, err = getMetadataKey("local-hostname"); err != nil {
return nil, err
}

if metadata.InstanceType, err = getMetadataKey("service-offering"); err != nil {
return nil, err
}

if metadata.InstanceID, err = getMetadataKey("instance-id"); err != nil {
return nil, err
}

if metadata.PublicIPv4, err = getMetadataKey("public-ipv4"); err != nil {
return nil, err
}

if metadata.Zone, err = getMetadataKey("availability-zone"); err != nil {
return nil, err
}

return metadata, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
addresses: []
links: []
routes: []
hostnames:
- hostname: talos
domainname: fqdn
layer: platform
resolvers: []
timeServers: []
operators: []
externalIPs:
- 1.2.3.4
metadata:
platform: cloudstack
hostname: talos.fqdn
instanceType: standard.tiny
instanceId: 3fe6b28a-669e-4eb2-bffd-4180c572c410
providerId: cloudstack://3fe6b28a-669e-4eb2-bffd-4180c572c410
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"local-hostname": "talos.fqdn",
"instance-id": "3fe6b28a-669e-4eb2-bffd-4180c572c410",
"public-ipv4": "1.2.3.4",
"service-offering": "standard.tiny"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/aws"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/azure"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/cloudstack"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/container"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/digitalocean"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/equinixmetal"
Expand Down Expand Up @@ -88,7 +89,7 @@ func NewPlatform(platform string) (p runtime.Platform, err error) {
return newPlatform(platform)
}

//nolint:gocyclo
//nolint:gocyclo,cyclop
func newPlatform(platform string) (p runtime.Platform, err error) {
switch platform {
case "akamai":
Expand All @@ -97,6 +98,8 @@ func newPlatform(platform string) (p runtime.Platform, err error) {
return aws.NewAWS()
case "azure":
p = &azure.Azure{}
case "cloudstack":
p = &cloudstack.Cloudstack{}
case "container":
p = &container.Container{}
case "digital-ocean":
Expand Down
12 changes: 12 additions & 0 deletions pkg/imager/profile/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ var Default = map[string]Profile{
},
},
},
"cloudstack": {
Platform: "cloudstack",
SecureBoot: pointer.To(false),
Output: Output{
Kind: OutKindImage,
OutFormat: OutFormatZSTD,
ImageOptions: &ImageOptions{
DiskSize: DefaultRAWDiskSize,
DiskFormat: DiskFormatRaw,
},
},
},
"digital-ocean": {
Platform: "digital-ocean",
SecureBoot: pointer.To(false),
Expand Down
5 changes: 3 additions & 2 deletions website/content/v1.8/introduction/support-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ description: "Table of supported Talos Linux versions and respective platforms."

| Talos Version | 1.8 | 1.7 |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Release Date | 2024-08-15 (TBD) | 2024-04-19 (1.7.0) |
| Release Date | 2024-09-15 (TBD) | 2024-04-19 (1.7.0) |
| End of Community Support | 1.9.0 release (2024-12-15, TBD) | 1.8.0 release (2024-08-15) |
| Enterprise Support | [offered by Sidero Labs Inc.](https://www.siderolabs.com/support/) | [offered by Sidero Labs Inc.](https://www.siderolabs.com/support/) |
| Kubernetes | 1.31, 1.30, 1.29, 1.28, 1.27, 1.26 | 1.30, 1.29, 1.28, 1.27, 1.26, 1.25 |
| NVIDIA Drivers | 550.x.x (PRODUCTION), 535.x.x (LTS) | 535.x.x (LTS) |
| Architecture | amd64, arm64 | amd64, arm64 |
| **Platforms** | | |
| - cloud | Akamai, AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenNebula, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | Akamai, AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenNebula, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud |
| - cloud | Akamai, AWS, GCP, Azure, CloudStack, Digital Ocean, Exoscale, Hetzner, OpenNebula, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | Akamai, AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenNebula, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud |
| - bare metal | x86: BIOS, UEFI, SecureBoot; arm64: UEFI, SecureBoot; boot: ISO, PXE, disk image | x86: BIOS, UEFI; arm64: UEFI; boot: ISO, PXE, disk image |
| - virtualized | VMware, Hyper-V, KVM, Proxmox, Xen | VMware, Hyper-V, KVM, Proxmox, Xen |
| - SBCs | Banana Pi M64, Jetson Nano, Libre Computer Board ALL-H3-CC, Nano Pi R4S, Pine64, Pine64 Rock64, Radxa ROCK Pi 4c, Radxa Rock4c+, Raspberry Pi 4B, Raspberry Pi Compute Module 4 | Banana Pi M64, Jetson Nano, Libre Computer Board ALL-H3-CC, Nano Pi R4S, Orange Pi R1 Plus LTS, Pine64, Pine64 Rock64, Radxa ROCK Pi 4c, Raspberry Pi 4B, Raspberry Pi Compute Module 4 |
Expand Down Expand Up @@ -45,6 +45,7 @@ description: "Table of supported Talos Linux versions and respective platforms."
### Tier 3

* Akamai
* CloudStack
* Exoscale
* Hetzner
* nocloud
Expand Down
Loading

0 comments on commit 75cecb4

Please sign in to comment.