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
2 changes: 2 additions & 0 deletions docs/api/divergence.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ names are currently defined:
Used by the CNS service.
* `triton.network.public` (string): Set on a container, used to specify the
external network name the instance will use.
* `triton.network.public_ipv4` (string): Set on a container, used to specify the
external network ip address the instance will use.

The `com.joyent.*` namespace is reserved for Triton specific use cases,
these label names are currently defined:
Expand Down
5 changes: 5 additions & 0 deletions docs/api/features/networks.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ Note that this this only overrides the default public network selection. This
means that when fabric networks are enabled you will still need to specify one
of `-p` or `-P` to get the public NIC.

The external network ipv4 address used by a container can be changed by setting
the `triton.network.public_ipv4` label to the desired ipv4 address that is
available in the `triton.network.public` network. The account must
be set as an owner on the `triton.network.public` network.

## Related

* [`sdc-fabric vlan`](https://apidocs.tritondatacenter.com/cloudapi/#CreateFabricVLAN) and `POST /my/fabrics/default/vlans` in CloudAPI
Expand Down
151 changes: 139 additions & 12 deletions lib/backends/sdc/networks.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/

/*
* Copyright (c) 2017, Joyent, Inc.
* Copyright 2021 Joyent, Inc.
* Copyright 2025 Bruce Smith
*/

/*
Expand All @@ -29,6 +30,8 @@ var ADMIN_NIC_TAG = 'admin';

// Label name used to set the external (public) network for a container.
var TRITON_PUBLIC_NETWORK_LABEL = 'triton.network.public';
var TRITON_PUBLIC_NETWORK_IPV4_LABEL = 'triton.network.public_ipv4';


var _napiClientCache; // set in `getNapiClient`

Expand Down Expand Up @@ -623,6 +626,8 @@ function externalNetworkByName(opts, container, payload, callback) {
assert.func(callback, 'callback');

var externalNetworkName;
var externalNetworkIP;

var labels = container.Labels || {};
var log = opts.log;

Expand All @@ -632,6 +637,12 @@ function externalNetworkByName(opts, container, payload, callback) {
externalNetworkName = labels[TRITON_PUBLIC_NETWORK_LABEL];
}

if (Object.prototype.hasOwnProperty.call(labels,
TRITON_PUBLIC_NETWORK_IPV4_LABEL))
{
externalNetworkIP = labels[TRITON_PUBLIC_NETWORK_IPV4_LABEL];
}

if (!payload.hasOwnProperty('networks')) {
payload.networks = [];
} else {
Expand Down Expand Up @@ -698,7 +709,26 @@ function externalNetworkByName(opts, container, payload, callback) {
return;
}

payload.networks.push({uuid: networks[0].uuid, primary: true});
var network = {ipv4_uuid: networks[0].uuid, primary: true};
// We land here if triton.network.public_ipv4 was provided.
if (externalNetworkIP) {
// We can only assign IPs to networks we are the owner on.
// Some networks can have no owner allowing us to provision
// but not assign addresses on them.
if (
networks[0].owner_uuids == null
|| networks[0].owner_uuids.indexOf(opts.account.uuid) === -1
) {
callback(new errors.ValidationError(util.format(
'%s label requires network ownership',
TRITON_PUBLIC_NETWORK_IPV4_LABEL)));
return;
}

network.ipv4_ips = [ externalNetworkIP ];
}

payload.networks.push(network);

callback();
return;
Expand Down Expand Up @@ -752,31 +782,48 @@ function addNetworksToContainerPayload(opts, container, payload, callback) {

vasync.pipeline({ funcs: [
function addFabricNetworks(_, next) {
if (!opts.config.overlay.enabled) {
next();
return;
}
networkMode = container.HostConfig.NetworkMode;
if (!networkMode || networkMode === 'bridge'
|| networkMode === 'default') {
var overlayEnabled = opts.config.overlay.enabled
var defaultNetworkMode = !networkMode || networkMode === 'bridge'
|| networkMode === 'default'

// Handle Fabrics
if (overlayEnabled && defaultNetworkMode) {
getDefaultFabricNetwork(opts,
function onGetDefaultFabricNet(getDefaultFabricNetErr,
defaultFabricNet) {
payload.networks =
[ {uuid: defaultFabricNet, primary: true} ];
next(getDefaultFabricNetErr);
});
} else {
findNetworkOrPoolByNameOrId(networkMode, opts,
function (findErr, network)
// Handle Non Fabrics
} else if (!defaultNetworkMode) {
findNetworkOrPoolByNameOrId(networkMode, opts,
function (findErr, network)
{
if (findErr) {
next(findErr);
return;
}
payload.networks = [ {uuid: network.uuid, primary: true} ];
payload.networks =
[ {ipv4_uuid: network.uuid, primary: true} ];
netCfg = container.NetworkingConfig
if (netCfg != null
&& netCfg.EndpointsConfig != null
&& netCfg.EndpointsConfig[networkMode]
!= undefined) {
var ipv4Addr =
container.NetworkingConfig.
EndpointsConfig[networkMode].IPAMConfig.
IPv4Address;
if (ipv4Addr) {
payload.networks[0].ipv4_ips = [ ipv4Addr ];
}
}
next();
});
} else {
next();
}
},

Expand All @@ -800,6 +847,86 @@ function addNetworksToContainerPayload(opts, container, payload, callback) {
externalNetworkByName(opts, container, payload, next);
},

// Enforce 2 distinct networks
function enforceDistinctNetworks(_, next) {
if (payload.networks.length === 2) {
var prevUUID;
for (var i = 0; i < payload.networks.length; i++) {
var nw = payload.networks[i];
// Handle both network and network obj
var netUUID = nw.ipv4_uuid || nw.uuid;
if (prevUUID === netUUID) {
next(new errors.ValidationError(util.format(
'both networks are of uuid: %s, networks must be '
+ 'distinct',
netUUID)));
return;
}
prevUUID = netUUID;
}
}
next();
},

// Enforce Publishing Ports for Non Fabric with > 1 network
function enforcePublishingPorts(_, next) {
// Handle publishing ports for non fabrics
if (payload.networks.length === 2
&& !containers.publishingPorts(container)) {
next(new errors.ValidationError(util.format(
'non fabrics with 2 networks requires a container with '
+ 'published ports'
)));
return;
}
next();
},

/*
* We need to verify that if a user passed in networks with IPs that none
* of the IPs are considered "managed". NAPI will handle other
* validations for us.
*/
function verifyNetworkIPs(_, next) {
var napi = getNapiClient(opts.config.napi);
var networksWithIps = [];
payload.networks.forEach(function forEachNetwork(net) {
// Today we only support passing in ipv4 addrs,
// but this should be extended to support ipv6 addrs
if (net.ipv4_ips && net.ipv4_ips.length > 0) {
networksWithIps.push(net);
}
});

vasync.forEachPipeline({
'func': function validateIp(network, done) {
napi.getIP(network.ipv4_uuid, network.ipv4_ips[0],
function napiGetIp(err, ip)
{
if (err) {
done(err);
return;
}
if (ip.belongs_to_type === 'other'
|| ip.owner_uuid === opts.config.adminUuid) {
done(new errors.InternalError(
'Cannot use Managed IP'));
return;
}
done(null, ip);
});
},
'inputs': networksWithIps
}, function (err) {
if (err) {
next(err);
return;
}
next();
return;
});
},

function runModifyProvisionNetworksPlugins(_, next) {
opts.app.plugins.modifyProvisionNetworks({
account: opts.account,
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sdc-docker",
"version": "0.7.11",
"version": "0.8.0",
"author": "MNX Cloud (mnx.io)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: we usually update the author line to:

"author": "Edgecast Cloud (edgecast.io)",

"private": true,
"dependencies": {
Expand All @@ -27,7 +27,7 @@
"strsplit": "1.0.0",
"tape": "^4.4.0",
"trace-event": "1.2.0",
"triton-tags": "1.4.0",
"triton-tags": "1.5.0",
"ufds": "1.7.1",
"vasync": "2.1.0",
"verror": "1.9.0",
Expand All @@ -44,4 +44,4 @@
"xtend": "^4.0.0"
},
"license": "MPL-2.0"
}
}
Loading