Skip to content

Commit

Permalink
Merge pull request kubev2v#131 from borod108/task/unique-vm-id
Browse files Browse the repository at this point in the history
Add persistent storage to agent
  • Loading branch information
borod108 authored Feb 6, 2025
2 parents 126a63b + fc8244b commit 405bac1
Show file tree
Hide file tree
Showing 17 changed files with 203 additions and 43 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/kind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
- name: Checkout the code
uses: actions/checkout@v2

- name: Install qemu-img
run: sudo apt update && sudo apt install -y qemu-utils

- name: Set env variables
run: |
export "REGISTRY_IP=$(ip addr show eth0 | grep -oP '(?<=inet\s)\d+\.\d+\.\d+\.\d+')"
Expand Down Expand Up @@ -73,11 +76,13 @@ jobs:
- name: Deploy assisted-migration
run: |
make deploy-on-kind MIGRATION_PLANNER_API_IMAGE=$MIGRATION_PLANNER_API_IMAGE MIGRATION_PLANNER_AGENT_IMAGE=$MIGRATION_PLANNER_AGENT_IMAGE MIGRATION_PLANNER_API_IMAGE_PULL_POLICY=$MIGRATION_PLANNER_API_IMAGE_PULL_POLICY INSECURE_REGISTRY=$INSECURE_REGISTRY MIGRATION_PLANNER_NAMESPACE=default
make deploy-on-kind MIGRATION_PLANNER_API_IMAGE=$MIGRATION_PLANNER_API_IMAGE MIGRATION_PLANNER_AGENT_IMAGE=$MIGRATION_PLANNER_AGENT_IMAGE MIGRATION_PLANNER_API_IMAGE_PULL_POLICY=$MIGRATION_PLANNER_API_IMAGE_PULL_POLICY INSECURE_REGISTRY=$INSECURE_REGISTRY MIGRATION_PLANNER_NAMESPACE=default PERSISTENT_DISK_DEVICE=/dev/vda
kubectl wait --for=condition=Ready pods --all --timeout=240s
kubectl port-forward --address 0.0.0.0 service/migration-planner-agent 7443:7443 &
kubectl port-forward --address 0.0.0.0 service/migration-planner 3443:3443 &
- name: Run test
run: |
mkdir /tmp/untarova
qemu-img convert -f vmdk -O qcow2 data/persistence-disk.vmdk /tmp/untarova/persistence-disk.qcow2
sudo make integration-test PLANNER_IP=${REGISTRY_IP}
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ deploy-on-kind:
sed "s|@MIGRATION_PLANNER_AGENT_IMAGE@|$(MIGRATION_PLANNER_AGENT_IMAGE)|g; \
s|@INSECURE_REGISTRY@|$(INSECURE_REGISTRY)|g; \
s|@MIGRATION_PLANNER_API_IMAGE_PULL_POLICY@|$(MIGRATION_PLANNER_API_IMAGE_PULL_POLICY)|g; \
s|@MIGRATION_PLANNER_API_IMAGE@|$(MIGRATION_PLANNER_API_IMAGE)|g" \
s|@MIGRATION_PLANNER_API_IMAGE@|$(MIGRATION_PLANNER_API_IMAGE)|g; \
s|@PERSISTENT_DISK_DEVICE@|$(PERSISTENT_DISK_DEVICE)|g" \
deploy/k8s/migration-planner.yaml.template > deploy/k8s/migration-planner.yaml
$(KUBECTL) apply -n "${MIGRATION_PLANNER_NAMESPACE}" -f 'deploy/k8s/*-service.yaml'
$(KUBECTL) apply -n "${MIGRATION_PLANNER_NAMESPACE}" -f 'deploy/k8s/*-secret.yaml'
Expand Down
24 changes: 15 additions & 9 deletions cmd/planner-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"os"
Expand Down Expand Up @@ -77,15 +76,15 @@ func (a *agentCmd) Execute() error {
undo := zap.ReplaceGlobals(logger)
defer undo()

agentID, err := a.readFile(agentFilename)
agentID, err := a.readFileFromPersistent(agentFilename)
if err != nil {
zap.S().Fatalf("failed to retreive agent_id: %v", err)
}

// Try to read jwt from file.
// We're assuming the jwt is valid.
// The agent will not try to validate the jwt. The backend is responsible for validating the token.
jwt, err := a.readFile(jwtFilename)
jwt, err := a.readFileFromVolatile(jwtFilename)
if err != nil {
zap.S().Errorf("failed to read jwt: %v", err)
}
Expand All @@ -97,16 +96,23 @@ func (a *agentCmd) Execute() error {
return nil
}

func (a *agentCmd) readFile(filename string) (string, error) {
// look for it in data dir
confDirPath := path.Join(a.config.DataDir, filename)
if _, err := os.Stat(confDirPath); err == nil {
content, err := os.ReadFile(confDirPath)
func (a *agentCmd) readFile(baseDir string, filename string) (string, error) {
filePath := path.Join(baseDir, filename)
if _, err := os.Stat(filePath); err == nil {
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
return string(bytes.TrimSpace(content)), nil
}

return "", errors.New("agent_id not found")
return "", fmt.Errorf("file not found: %s", filePath)
}

func (a *agentCmd) readFileFromVolatile(filename string) (string, error) {
return a.readFile(a.config.DataDir, filename)
}

func (a *agentCmd) readFileFromPersistent(filename string) (string, error) {
return a.readFile(a.config.PersistentDataDir, filename)
}
28 changes: 23 additions & 5 deletions data/MigrationAssessment.ovf
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<?xml version='1.0' encoding='UTF-8'?>
<Envelope xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:vmw="http://www.vmware.com/schema/ovf" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData">
<References>
<File ovf:id="file1" ovf:href="MigrationAssessment.iso" ovf:size="1220542464"/>
<File ovf:href="MigrationAssessment.iso" ovf:id="file1" ovf:size="1244659712"/>
<File ovf:href="persistence-disk.vmdk" ovf:id="file2" ovf:size="68096"/>
</References>
<DiskSection>
<Info>Virtual disk information</Info>
<Disk ovf:capacity="50" ovf:capacityAllocationUnits="byte * 2^20" ovf:diskId="vmdisk1" ovf:fileRef="file2" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" ovf:populatedSize="0"/>
</DiskSection>
<NetworkSection>
<Info>The list of logical networks</Info>
<Network ovf:name="routable-network">
Expand All @@ -12,15 +17,16 @@
<VirtualSystem ovf:id="MigrationAssessment">
<Info>A Virtual system</Info>
<Name>MigrationAssessment</Name>
<OperatingSystemSection ovf:id="80" ovf:version="8" vmw:osType="rhel9_64Guest">
<Info>The operating system installed</Info>
<Description>Other Linux (64-bit)</Description>
<OperatingSystemSection ovf:id="80" ovf:version="9" vmw:osType="rhel9_64Guest">
<Info>The kind of installed guest operating system</Info>
<Description>Red Hat Enterprise Linux 9 (64-bit)</Description>
</OperatingSystemSection>
<VirtualHardwareSection>
<Info>Virtual hardware requirements</Info>
<System>
<vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
<vssd:InstanceID>0</vssd:InstanceID>
<vssd:VirtualSystemIdentifier>MigrationAssessment</vssd:VirtualSystemIdentifier>
<vssd:VirtualSystemType>vmx-20</vssd:VirtualSystemType>
</System>
<Item>
Expand All @@ -44,11 +50,12 @@
<rasd:Description>SCSI Controller</rasd:Description>
<rasd:ElementName>SCSI Controller 1</rasd:ElementName>
<rasd:InstanceID>3</rasd:InstanceID>
<rasd:ResourceSubType>VirtualSCSI</rasd:ResourceSubType>
<rasd:ResourceType>6</rasd:ResourceType>
<vmw:Config ovf:required="false" vmw:key="slotInfo.pciSlotNumber" vmw:value="16"/>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:Address>1</rasd:Address>
<rasd:Description>IDE Controller</rasd:Description>
<rasd:ElementName>IDE Controller 1</rasd:ElementName>
<rasd:InstanceID>4</rasd:InstanceID>
Expand Down Expand Up @@ -76,6 +83,17 @@
<vmw:Config ovf:required="false" vmw:key="wakeOnLanEnabled" vmw:value="true"/>
<vmw:Config ovf:required="false" vmw:key="connectable.allowGuestControl" vmw:value="false"/>
</Item>
<Item>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
<rasd:ElementName>Hard disk 1</rasd:ElementName>
<rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
<rasd:InstanceID>10</rasd:InstanceID>
<rasd:Parent>3</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
<vmw:Config ovf:required="false" vmw:key="backing.writeThrough" vmw:value="false"/>
<vmw:Config ovf:required="false" vmw:key="backing.diskMode" vmw:value="independent_persistent"/>
<vmw:Config ovf:required="false" vmw:key="guestReadOnly" vmw:value="false"/>
</Item>
<Item ovf:required="false">
<rasd:ElementName>Video card</rasd:ElementName>
<rasd:InstanceID>7</rasd:InstanceID>
Expand Down
25 changes: 22 additions & 3 deletions data/ignition.template
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,32 @@ systemd:
contents: |
[Unit]
Description=Service to retrieve system uuid
Requires=var-lib-data.mount
After=var-lib-data.mount
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'cat /sys/class/dmi/id/product_uuid > /var/home/core/.migration-planner/data/agent_id'
ExecStartPost=/bin/bash -c 'chown core:core /var/home/core/.migration-planner/data/agent_id'
ExecStart=/bin/bash -c 'cat /sys/class/dmi/id/product_uuid > /var/lib/data/agent_id'
ExecStartPost=/bin/bash -c 'chown -R core:core /var/lib/data'
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
- name: var-lib-data.mount
enabled: true
contents: |
[Mount]
What=/dev/disk/by-label/DATA
Where=/var/lib/data
Type=ext4
[Install]
WantedBy=local-fs.target

storage:
filesystems:
- path: /var/lib/data
device: {{ .PersistentDiskDevice }}
format: ext4
label: "DATA"

links:
- path: /home/core/.config/systemd/user/timers.target.wants/podman-auto-update.timer
target: /usr/lib/systemd/user/podman-auto-update.timer
Expand Down Expand Up @@ -88,6 +105,7 @@ storage:
inline: |
config-dir: /agent/config
data-dir: /agent/data
persistent-data-dir: /agent/persistent-data
www-dir: /app/www
log-level: debug
update-interval: 5s
Expand Down Expand Up @@ -116,14 +134,15 @@ storage:
Exec= -config /agent/config/config.yaml
PublishPort=3333:3333
Volume=/home/core/.migration-planner:/agent:Z
Volume=/var/lib/data:/agent/persistent-data:Z
Environment=OPA_SERVER=127.0.0.1:8181
Network=host
UserNS=keep-id:uid=1001

[Service]
Restart=on-failure
RestartSec=5
ExecStartPre=/bin/bash -c 'until [ -f /var/home/core/.migration-planner/data/agent_id ]; do sleep 1; done'
ExecStartPre=/bin/bash -c 'until [ -f /var/lib/data/agent_id ]; do sleep 1; done'

[Install]
WantedBy=multi-user.target default.target
Expand Down
Binary file added data/persistence-disk.vmdk
Binary file not shown.
2 changes: 2 additions & 0 deletions deploy/k8s/migration-planner.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ spec:
value: @MIGRATION_PLANNER_AGENT_IMAGE@
- name: INSECURE_REGISTRY
value: "@INSECURE_REGISTRY@"
- name: PERSISTENT_DISK_DEVICE
value: "@PERSISTENT_DISK_DEVICE@"
volumeMounts:
volumeMounts:
- name: migration-planner-config
Expand Down
2 changes: 1 addition & 1 deletion internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (a *Agent) start(ctx context.Context, plannerClient client.Planner) {
healthChecker.Start(ctx, a.healtCheckStopCh)
statusUpdater.HealthChecker = healthChecker

collector := service.NewCollector(a.config.DataDir)
collector := service.NewCollector(a.config.DataDir, a.config.PersistentDataDir)
collector.Collect(ctx)

updateTicker := jitterbug.New(time.Duration(a.config.UpdateInterval.Duration), &jitterbug.Norm{Stdev: 30 * time.Millisecond, Mean: 0})
Expand Down
1 change: 1 addition & 0 deletions internal/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ var _ = Describe("Agent", func() {
config := config.Config{
PlannerService: config.PlannerService{Config: *client.NewDefault()},
DataDir: agentTmpFolder,
PersistentDataDir: agentTmpFolder,
ConfigDir: agentTmpFolder,
UpdateInterval: util.Duration{Duration: updateInterval},
HealthCheckInterval: 10,
Expand Down
7 changes: 6 additions & 1 deletion internal/agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
DefaultConfigFile = DefaultConfigDir + "/config.yaml"
// DefaultDataDir is the default directory where the agent's data is stored
DefaultDataDir = "/var/lib/planner"
// DefaultPersistentDataDir is the default directory where the agent's data is stored
DefaultPersistentDataDir = "/var/lib/data"
// DefaultWwwDir is the default directory from which the agent serves static files
DefaultWwwDir = "/var/www/planner"
// DefaultPlannerEndpoint is the default address of the migration planner server
Expand All @@ -48,6 +50,8 @@ type Config struct {
ConfigDir string `json:"config-dir"`
// DataDir is the directory where the agent's data is stored
DataDir string `json:"data-dir"`
// PersistentDataDir is the directory where persistent data is stored
PersistentDataDir string `json:"persistent-data-dir"`
// WwwDir is the directory from which the agent serves static files
WwwDir string `json:"www-dir"`
// SourceID is the ID of this source in the planner
Expand Down Expand Up @@ -85,6 +89,7 @@ func NewDefault() *Config {
c := &Config{
ConfigDir: DefaultConfigDir,
DataDir: DefaultDataDir,
PersistentDataDir: DefaultPersistentDataDir,
WwwDir: DefaultWwwDir,
PlannerService: PlannerService{Config: *client.NewDefault()},
UpdateInterval: util.Duration{Duration: DefaultUpdateInterval},
Expand All @@ -102,14 +107,14 @@ func (cfg *Config) Validate() error {
if err := cfg.PlannerService.Validate(); err != nil {
return err
}

requiredFields := []struct {
value string
name string
checkPath bool
}{
{cfg.ConfigDir, "config-dir", true},
{cfg.DataDir, "data-dir", true},
{cfg.PersistentDataDir, "persistent-data-dir", true},
}

for _, field := range requiredFields {
Expand Down
2 changes: 1 addition & 1 deletion internal/agent/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func RegisterApi(router *chi.Mux, statusUpdater *service.StatusUpdater, configur
_ = render.Render(w, r, ServiceUIReply{URL: configuration.PlannerService.Service.UI})
})
router.Put("/api/v1/credentials", func(w http.ResponseWriter, r *http.Request) {
credentialHandler(configuration.DataDir, w, r)
credentialHandler(configuration.PersistentDataDir, w, r)
})
router.Get("/api/v1/inventory", func(w http.ResponseWriter, r *http.Request) {
data, err := getInventory(configuration.DataDir)
Expand Down
12 changes: 7 additions & 5 deletions internal/agent/service/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ import (
)

type Collector struct {
dataDir string
once sync.Once
dataDir string
credentialsDir string
once sync.Once
}

func NewCollector(dataDir string) *Collector {
func NewCollector(dataDir string, credentialsDir string) *Collector {
return &Collector{
dataDir: dataDir,
dataDir: dataDir,
credentialsDir: credentialsDir,
}
}

Expand All @@ -56,7 +58,7 @@ func (c *Collector) Collect(ctx context.Context) {
}

func (c *Collector) run() {
credentialsFilePath := filepath.Join(c.dataDir, config.CredentialsFile)
credentialsFilePath := filepath.Join(c.credentialsDir, config.CredentialsFile)
zap.S().Named("collector").Infof("Waiting for credentials")
waitForFile(credentialsFilePath)

Expand Down
2 changes: 1 addition & 1 deletion internal/agent/service/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (s *StatusUpdater) UpdateStatus(ctx context.Context, status api.AgentStatus

func (s *StatusUpdater) CalculateStatus() (api.AgentStatus, string, *api.Inventory) {
inventoryFilePath := filepath.Join(s.config.DataDir, config.InventoryFile)
credentialsFilePath := filepath.Join(s.config.DataDir, config.CredentialsFile)
credentialsFilePath := filepath.Join(s.config.PersistentDataDir, config.CredentialsFile)
reader := fileio.NewReader()

err := reader.CheckPathExists(credentialsFilePath)
Expand Down
Loading

0 comments on commit 405bac1

Please sign in to comment.