Skip to content
Merged
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
21 changes: 13 additions & 8 deletions .github/workflows/branch-dev-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,20 @@ jobs:
const tag = process.env.DEV_RELEASE_TAG;
const { owner, repo } = context.repo;

try {
const release = await github.rest.repos.getReleaseByTag({ owner, repo, tag });
await github.rest.repos.deleteRelease({ owner, repo, release_id: release.data.id });
core.info(`Deleted existing release for ${tag}`);
} catch (error) {
if (error.status !== 404) {
throw error;
}
const releases = await github.paginate(github.rest.repos.listReleases, {
owner,
repo,
per_page: 100,
});
const matchingReleases = releases.filter((release) => release.tag_name === tag);

if (matchingReleases.length === 0) {
core.info(`No existing release found for ${tag}`);
} else {
for (const release of matchingReleases) {
await github.rest.repos.deleteRelease({ owner, repo, release_id: release.id });
core.info(`Deleted existing release ${release.id} for ${tag}`);
}
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,31 @@ public class CreateSharedFSCmd extends BaseAsyncCreateCmd implements UserCmd {
description = "network to attach the shared filesystem to")
private Long networkId;

@Parameter(name = "networkmode",
type = CommandType.STRING,
description = "network addressing mode for an L2 SharedFS network: DHCP or STATIC")
private String networkMode;

@Parameter(name = "ipcidr",
type = CommandType.STRING,
description = "static IPv4 address and prefix for the SharedFS VM, for example 10.10.1.211/24")
private String ipCidr;

@Parameter(name = ApiConstants.GATEWAY,
type = CommandType.STRING,
description = "optional static IPv4 default gateway")
private String gateway;

@Parameter(name = ApiConstants.DNS1,
type = CommandType.STRING,
description = "optional primary IPv4 DNS server")
private String dns1;

@Parameter(name = ApiConstants.DNS2,
type = CommandType.STRING,
description = "optional secondary IPv4 DNS server")
private String dns2;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -218,6 +243,33 @@ public Long getNetworkId() {
return networkId;
}

public SharedFS.NetworkMode getNetworkMode() {
if (networkMode == null) {
return SharedFS.NetworkMode.DHCP;
}
try {
return SharedFS.NetworkMode.valueOf(networkMode.trim().toUpperCase());
} catch (IllegalArgumentException e) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid network mode. Supported values are DHCP and STATIC.");
}
}

public String getIpCidr() {
return ipCidr;
}

public String getGateway() {
return gateway;
}

public String getDns1() {
return dns1;
}

public String getDns2() {
return dns2;
}

public String getSharedFSProviderName() {
if (sharedFSProviderName != null) {
return sharedFSProviderName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,34 @@ public class SharedFSResponse extends BaseResponseWithTagInformation implements
@Param(description = "Network name of the shared filesystem")
private String networkName;

@SerializedName("networkmode")
@Param(description = "SharedFS VM network addressing mode")
private String networkMode;

@SerializedName("ipcidr")
@Param(description = "requested static IPv4 address and prefix")
private String ipCidr;

@SerializedName(ApiConstants.IP_ADDRESS)
@Param(description = "requested static IPv4 address")
private String ipAddress;

@SerializedName(ApiConstants.CIDR)
@Param(description = "requested static IPv4 CIDR")
private String cidr;

@SerializedName(ApiConstants.GATEWAY)
@Param(description = "requested static IPv4 gateway")
private String gateway;

@SerializedName(ApiConstants.DNS1)
@Param(description = "requested primary DNS server")
private String dns1;

@SerializedName(ApiConstants.DNS2)
@Param(description = "requested secondary DNS server")
private String dns2;

@SerializedName(ApiConstants.NIC)
@Param(description = "the list of nics associated with the shared filesystem", responseObject = NicResponse.class)
private List<NicResponse> nics;
Expand Down Expand Up @@ -258,6 +286,34 @@ public void setNetworkName(String networkName) {
this.networkName = networkName;
}

public void setNetworkMode(String networkMode) {
this.networkMode = networkMode;
}

public void setIpCidr(String ipCidr) {
this.ipCidr = ipCidr;
}

public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}

public void setCidr(String cidr) {
this.cidr = cidr;
}

public void setGateway(String gateway) {
this.gateway = gateway;
}

public void setDns1(String dns1) {
this.dns1 = dns1;
}

public void setDns2(String dns2) {
this.dns2 = dns2;
}

public List<NicResponse> getNics() {
return nics;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ enum Protocol {
NFS
}

enum NetworkMode {
DHCP, STATIC
}

enum State {
Allocated(false, "The shared filesystem is allocated in db but hasn't been created or started yet."),
Ready(false, "The shared filesystem is ready to use."),
Expand Down Expand Up @@ -181,6 +185,30 @@ static String getSharedFSPath() {

void setServiceOfferingId(Long serviceOfferingId);

NetworkMode getNetworkMode();

void setNetworkMode(NetworkMode networkMode);

String getIpAddress();

void setIpAddress(String ipAddress);

String getCidr();

void setCidr(String cidr);

String getGateway();

void setGateway(String gateway);

String getDns1();

void setDns1(String dns1);

String getDns2();

void setDns2(String dns2);

Date getUpdated();

public long getUpdatedCount();
Expand Down
88 changes: 88 additions & 0 deletions docs/design/diplo-sharedfs-l2-static-network.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# SharedFS L2 static network without ConfigDrive

## Goal

Allow a Storage Service SharedFS VM attached to an L2 guest network to use an
operator-supplied IPv4 address even when the network has neither DHCP nor
ConfigDrive. The change is scoped to SharedFS VMs and must not alter the boot or
network behavior of routers, consoles, secondary-storage VMs, or ordinary user
VMs.

## Behavioral contract

| Area | DHCP mode | L2 static mode |
|---|---|---|
| Existing behavior | Unchanged | New opt-in path |
| UserData requirement | Preserved | Not required |
| Address source | Existing network services | Required `ipcidr` in IPv4/prefix form; optional `gateway`, `dns1`, and `dns2` |
| Cloud DB NIC address | Existing allocation | Requested address is reserved at VM deployment |
| Guest configuration | Existing template path | Fixed QGA operation after VM start |
| Reboot persistence | Existing template path | SharedFS-only oneshot systemd unit inside that VM |
| SystemVM template | Unchanged | Unchanged |

## API and persistence

`createSharedFileSystem` gains optional `networkmode`, `ipcidr`,
`gateway`, `dns1`, and `dns2` parameters. Gateway and DNS values are optional.
`ipcidr` uses host-address/prefix notation such as `10.10.1.211/24`.
`networkmode` is `DHCP` by default or
`STATIC` for the new path. The management server calculates the network address
from any valid prefix from `/0` through `/32`, then persists the host address and
the calculated network CIDR on `shared_filesystem`,
exposed by `listSharedFileSystems`, and included in
`shared_filesystem_view`. Persisting desired state before deployment lets
start, restart, redeploy, and diagnostics use the same values without changing
the `SharedFSLifeCycle` interface signature.

## Server validation

Static mode is accepted only when the selected network has guest type `L2`.
The server validates all supplied IPv4 values, requires the address and any
supplied gateway to be in the CIDR, rejects network/broadcast addresses, and rejects an address
already assigned to a NIC in the same network. DHCP mode keeps the existing
UserData/ConfigDrive capability check.

The lifecycle receives the requested IPv4 through the persisted SharedFS
entity and passes it to `createAdvancedVirtualMachine`. This reserves the
address and records it on the VM NIC while keeping the existing lifecycle
method signature intact.

## QGA application path

The host agent recognizes one exact operation,
`configure-sharedfs-static-network`. Other Storage Service operations continue
to invoke `/usr/local/bin/ablestack-storagectl` unchanged.

The fixed operation accepts only JSON data produced by the management server.
It installs a SharedFS-specific network state file, helper, and oneshot unit in
the guest. The helper resolves the interface by its VM NIC MAC address, applies
the IPv4 address, conditionally applies a default route and DNS resolver entries,
and verifies the resulting configured state. The unit runs on
boot only when the SharedFS state file exists. No arbitrary command or script
is accepted from the API.

The management server retries dispatch while QGA becomes ready. Failure to
apply or verify the requested network fails the SharedFS deployment instead of
reporting a ready but unreachable service.

## UI

The create dialog identifies L2 networks from the `listNetworks.type` field.
For L2 networks with UserData, DHCP is the default and static remains selectable.
For L2 networks without UserData/ConfigDrive, static mode is selected automatically
and DHCP is disabled. Static mode reveals one required IPv4/prefix field plus optional
gateway, DNS 1, and DNS 2 fields using existing theme tokens. Changing networks
clears stale static values and selects the valid default mode for the new network.

## Verification gates

1. API/server unit tests cover validation, persistence, DHCP compatibility,
and QGA dispatch.
2. KVM wrapper tests prove that only the exact static-network operation bypasses
`ablestack-storagectl` and that generic operations remain unchanged.
3. UI tests prove conditional request construction.
4. The management backend, UI, and KVM agent plugin are deployed together.
5. Test deployment verifies management/UI health and deployed asset hashes.
6. End-to-end acceptance creates an L2 SharedFS in a no-DHCP network, confirms
the requested address through QGA, validates connectivity, reboots the VM,
and confirms that the same address and any configured route and DNS return.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ public class SharedFSVO implements SharedFS {
@Column(name = "service_offering_id")
private Long serviceOfferingId;

@Column(name = "network_mode")
@Enumerated(value = EnumType.STRING)
private NetworkMode networkMode = NetworkMode.DHCP;

@Column(name = "ip_address")
private String ipAddress;

@Column(name = "cidr")
private String cidr;

@Column(name = "gateway")
private String gateway;

@Column(name = "dns1")
private String dns1;

@Column(name = "dns2")
private String dns2;

@Column(name = "updated")
@Temporal(value = TemporalType.TIMESTAMP)
Date updated;
Expand Down Expand Up @@ -228,6 +247,66 @@ public void setServiceOfferingId(Long serviceOfferingId) {
this.serviceOfferingId = serviceOfferingId;
}

@Override
public NetworkMode getNetworkMode() {
return networkMode == null ? NetworkMode.DHCP : networkMode;
}

@Override
public void setNetworkMode(NetworkMode networkMode) {
this.networkMode = networkMode;
}

@Override
public String getIpAddress() {
return ipAddress;
}

@Override
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}

@Override
public String getCidr() {
return cidr;
}

@Override
public void setCidr(String cidr) {
this.cidr = cidr;
}

@Override
public String getGateway() {
return gateway;
}

@Override
public void setGateway(String gateway) {
this.gateway = gateway;
}

@Override
public String getDns1() {
return dns1;
}

@Override
public void setDns1(String dns1) {
this.dns1 = dns1;
}

@Override
public String getDns2() {
return dns2;
}

@Override
public void setDns2(String dns2) {
this.dns2 = dns2;
}

@Override
public Date getUpdated() {
return updated;
Expand Down
Loading
Loading