Skip to content

Commit b6d0e7d

Browse files
committed
Initial integration with Oxide Packet Transformation Engine
- Brings in OPTE via the `opte-ioctl` and `opte` crates. - Modifies the instance-ensure request from Nexus to the sled agent, to carry the actual information required for setting up the guest OPTE port. This includes the actual IP subnet and MAC, rather than things like the VPC Subnet UUID. - Adds a database query and method to extract the above information from both the network interface and VPC subnet tables. - Adds OPTE port for the guests (and currently still a VNIC on top), with the right OPTE settings for traffic to flow between two guests in the same VPC subnet. That's the virtual-to-physical mapping and a router entry for the subnet. - Adds the VNICs over each OPTE port to the running zone. Note that this removes the specification of guest NICs for the zone itself as VNICs. They are passed as OPTE ports, and the VNIC is pulled out internally, so hopefully little will need to change when the VNIC is removed entirely. - Store the main underlay address for the sled agent, currently its dropshot server IP address, in the instance manager, and forward to each instance. It's then used as the underlay address when setting up the OPTE ports for the guest. - Fixes bad naming of VNICs used to emulate Chelsio NICs in the current setup. Removes the unnecessary loopback address, since the sled agent provides its main listening address as the underlay address when create OPTE ports. - Adds better installation of OPTE, xde, and the kernel bits they rely on. Don't install `opteadm` or `xde` directly, do everything through their package repos.
1 parent 3bdbdc4 commit b6d0e7d

File tree

23 files changed

+1412
-416
lines changed

23 files changed

+1412
-416
lines changed

Cargo.lock

Lines changed: 666 additions & 145 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/src/api/external/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,15 @@ impl FromStr for IpNet {
11681168
}
11691169
}
11701170

1171+
impl From<IpNet> for ipnetwork::IpNetwork {
1172+
fn from(net: IpNet) -> ipnetwork::IpNetwork {
1173+
match net {
1174+
IpNet::V4(net) => ipnetwork::IpNetwork::from(net.0),
1175+
IpNet::V6(net) => ipnetwork::IpNetwork::from(net.0),
1176+
}
1177+
}
1178+
}
1179+
11711180
/// A `RouteTarget` describes the possible locations that traffic matching a
11721181
/// route destination can be sent.
11731182
#[derive(

nexus/src/db/datastore.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ use diesel::query_dsl::methods::LoadQuery;
6464
use diesel::upsert::excluded;
6565
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
6666
use omicron_common::api;
67+
use omicron_common::api::external;
6768
use omicron_common::api::external::DataPageParams;
6869
use omicron_common::api::external::DeleteResult;
6970
use omicron_common::api::external::Error;
@@ -76,6 +77,7 @@ use omicron_common::api::external::{
7677
CreateResult, IdentityMetadataCreateParams,
7778
};
7879
use omicron_common::bail_unless;
80+
use sled_agent_client::types as sled_client_types;
7981
use std::convert::{TryFrom, TryInto};
8082
use std::net::Ipv6Addr;
8183
use std::sync::Arc;
@@ -1321,6 +1323,83 @@ impl DataStore {
13211323
Ok(())
13221324
}
13231325

1326+
/// Return the information about an instance's network interfaces required
1327+
/// for the sled agent to instantiate it via OPTE.
1328+
///
1329+
/// OPTE requires information that's currently split across the network
1330+
/// interface and VPC subnet tables. This query just joins those for each
1331+
/// NIC in the given instance.
1332+
pub(crate) async fn derive_guest_network_interface_info(
1333+
&self,
1334+
opctx: &OpContext,
1335+
authz_instance: &authz::Instance,
1336+
) -> ListResultVec<sled_client_types::NetworkInterface> {
1337+
opctx.authorize(authz::Action::ListChildren, authz_instance).await?;
1338+
1339+
use db::schema::network_interface;
1340+
use db::schema::vpc_subnet;
1341+
1342+
// The record type for the results of the below JOIN query
1343+
#[derive(Debug, diesel::Queryable)]
1344+
struct NicInfo {
1345+
name: db::model::Name,
1346+
ip: ipnetwork::IpNetwork,
1347+
mac: db::model::MacAddr,
1348+
ipv4_block: db::model::Ipv4Net,
1349+
ipv6_block: db::model::Ipv6Net,
1350+
vpc_id: Uuid,
1351+
slot: i16,
1352+
}
1353+
1354+
impl From<NicInfo> for sled_client_types::NetworkInterface {
1355+
fn from(nic: NicInfo) -> sled_client_types::NetworkInterface {
1356+
let ip_subnet = if nic.ip.is_ipv4() {
1357+
external::IpNet::V4(nic.ipv4_block.0)
1358+
} else {
1359+
external::IpNet::V6(nic.ipv6_block.0)
1360+
};
1361+
sled_client_types::NetworkInterface {
1362+
name: sled_client_types::Name::from(&nic.name.0),
1363+
ip: nic.ip.ip().to_string(),
1364+
mac: sled_client_types::MacAddr::from(nic.mac.0),
1365+
subnet: sled_client_types::IpNet::from(ip_subnet),
1366+
vpc_id: nic.vpc_id,
1367+
slot: u8::try_from(nic.slot).unwrap(),
1368+
}
1369+
}
1370+
}
1371+
1372+
let rows = network_interface::table
1373+
.filter(network_interface::instance_id.eq(authz_instance.id()))
1374+
.filter(network_interface::time_deleted.is_null())
1375+
.inner_join(
1376+
vpc_subnet::table
1377+
.on(network_interface::subnet_id.eq(vpc_subnet::id)),
1378+
)
1379+
.order_by(network_interface::slot)
1380+
// TODO-cleanup: Having to specify each column again is less than
1381+
// ideal. However, this type doesn't appear to have the `Row`
1382+
// associated type in the documentation. DRY this out.
1383+
.select((
1384+
network_interface::name,
1385+
network_interface::ip,
1386+
network_interface::mac,
1387+
vpc_subnet::ipv4_block,
1388+
vpc_subnet::ipv6_block,
1389+
network_interface::vpc_id,
1390+
network_interface::slot,
1391+
))
1392+
.get_results_async::<NicInfo>(self.pool_authorized(opctx).await?)
1393+
.await
1394+
.map_err(|e| {
1395+
public_error_from_diesel_pool(e, ErrorHandler::Server)
1396+
})?;
1397+
Ok(rows
1398+
.into_iter()
1399+
.map(sled_client_types::NetworkInterface::from)
1400+
.collect())
1401+
}
1402+
13241403
/// List network interfaces associated with a given instance.
13251404
pub async fn instance_list_network_interfaces(
13261405
&self,

nexus/src/nexus.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,8 +1686,12 @@ impl Nexus {
16861686
});
16871687
}
16881688

1689-
let nics: Vec<external::NetworkInterface> = self
1689+
let nics = self
16901690
.db_datastore
1691+
.derive_guest_network_interface_info(&opctx, &authz_instance)
1692+
.await?;
1693+
1694+
/*
16911695
.instance_list_network_interfaces(
16921696
&opctx,
16931697
&authz_instance,
@@ -1702,6 +1706,7 @@ impl Nexus {
17021706
.iter()
17031707
.map(|x| x.clone().into())
17041708
.collect();
1709+
*/
17051710

17061711
// Ask the sled agent to begin the state change. Then update the
17071712
// database to reflect the new intermediate state. If this update is
@@ -1712,7 +1717,7 @@ impl Nexus {
17121717
runtime: sled_agent_client::types::InstanceRuntimeState::from(
17131718
db_instance.runtime().clone(),
17141719
),
1715-
nics: nics.iter().map(|nic| nic.clone().into()).collect(),
1720+
nics,
17161721
disks: disk_reqs,
17171722
cloud_init_bytes: Some(base64::encode(
17181723
db_instance.generate_cidata()?,

nexus/src/sagas.rs

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ use omicron_common::api::external::Generation;
2828
use omicron_common::api::external::IdentityMetadataCreateParams;
2929
use omicron_common::api::external::InstanceState;
3030
use omicron_common::api::external::Name;
31-
use omicron_common::api::external::NetworkInterface;
3231
use omicron_common::api::internal::nexus::InstanceRuntimeState;
3332
use omicron_common::backoff::{self, BackoffError};
3433
use rand::{rngs::StdRng, RngCore, SeedableRng};
@@ -326,9 +325,9 @@ async fn sic_allocate_network_interface_ids(
326325

327326
async fn sic_create_network_interfaces(
328327
sagactx: ActionContext<SagaInstanceCreate>,
329-
) -> Result<Option<Vec<NetworkInterface>>, ActionError> {
328+
) -> Result<(), ActionError> {
330329
match sagactx.saga_params().create_params.network_interfaces {
331-
params::InstanceNetworkInterfaceAttachment::None => Ok(None),
330+
params::InstanceNetworkInterfaceAttachment::None => Ok(()),
332331
params::InstanceNetworkInterfaceAttachment::Default => {
333332
sic_create_default_network_interface(&sagactx).await
334333
}
@@ -345,9 +344,9 @@ async fn sic_create_network_interfaces(
345344
async fn sic_create_custom_network_interfaces(
346345
sagactx: &ActionContext<SagaInstanceCreate>,
347346
interface_params: &[params::NetworkInterfaceCreate],
348-
) -> Result<Option<Vec<NetworkInterface>>, ActionError> {
347+
) -> Result<(), ActionError> {
349348
if interface_params.is_empty() {
350-
return Ok(Some(vec![]));
349+
return Ok(());
351350
}
352351

353352
let osagactx = sagactx.user_data();
@@ -381,7 +380,6 @@ async fn sic_create_custom_network_interfaces(
381380
)));
382381
}
383382

384-
let mut interfaces = Vec::with_capacity(interface_params.len());
385383
if ids.len() != interface_params.len() {
386384
return Err(ActionError::action_failed(Error::internal_error(
387385
"found differing number of network interface IDs and interface \
@@ -392,7 +390,7 @@ async fn sic_create_custom_network_interfaces(
392390
// TODO-correctness: It seems racy to fetch the subnet and create the
393391
// interface in separate requests, but outside of a transaction. This
394392
// should probably either be in a transaction, or the
395-
// `subnet_create_network_interface` function/query needs some JOIN
393+
// `instance_create_network_interface` function/query needs some JOIN
396394
// on the `vpc_subnet` table.
397395
let (.., authz_subnet, db_subnet) = LookupPath::new(&opctx, &datastore)
398396
.vpc_id(authz_vpc.id())
@@ -406,7 +404,7 @@ async fn sic_create_custom_network_interfaces(
406404
interface_id,
407405
instance_id,
408406
authz_vpc.id(),
409-
db_subnet,
407+
db_subnet.clone(),
410408
mac,
411409
params.identity.clone(),
412410
params.ip,
@@ -422,8 +420,8 @@ async fn sic_create_custom_network_interfaces(
422420
.await;
423421

424422
use crate::db::subnet_allocation::NetworkInterfaceError;
425-
let interface = match result {
426-
Ok(interface) => Ok(interface),
423+
match result {
424+
Ok(_) => Ok(()),
427425

428426
// Detect the specific error arising from this node being partially
429427
// completed.
@@ -453,30 +451,19 @@ async fn sic_create_custom_network_interfaces(
453451
instance_id;
454452
"primary_key" => interface_id.to_string(),
455453
);
456-
457-
// Refetch the interface itself, to serialize it for the next
458-
// saga node.
459-
LookupPath::new(&opctx, &datastore)
460-
.instance_id(authz_instance.id())
461-
.network_interface_name(&db::model::Name(
462-
params.identity.name.clone(),
463-
))
464-
.fetch()
465-
.await
466-
.map(|(.., db_interface)| db_interface)
454+
Ok(())
467455
}
468456
Err(e) => Err(e.into_external()),
469457
}
470458
.map_err(ActionError::action_failed)?;
471-
interfaces.push(NetworkInterface::from(interface))
472459
}
473-
Ok(Some(interfaces))
460+
Ok(())
474461
}
475462

476463
/// Create the default network interface for an instance during the create saga
477464
async fn sic_create_default_network_interface(
478465
sagactx: &ActionContext<SagaInstanceCreate>,
479-
) -> Result<Option<Vec<NetworkInterface>>, ActionError> {
466+
) -> Result<(), ActionError> {
480467
let osagactx = sagactx.user_data();
481468
let datastore = osagactx.datastore();
482469
let saga_params = sagactx.saga_params();
@@ -519,13 +506,13 @@ async fn sic_create_default_network_interface(
519506
interface_id,
520507
instance_id,
521508
authz_vpc.id(),
522-
db_subnet,
509+
db_subnet.clone(),
523510
mac,
524511
interface_params.identity.clone(),
525512
interface_params.ip,
526513
)
527514
.map_err(ActionError::action_failed)?;
528-
let interface = datastore
515+
datastore
529516
.instance_create_network_interface(
530517
&opctx,
531518
&authz_subnet,
@@ -535,17 +522,17 @@ async fn sic_create_default_network_interface(
535522
.await
536523
.map_err(db::subnet_allocation::NetworkInterfaceError::into_external)
537524
.map_err(ActionError::action_failed)?;
538-
Ok(Some(vec![interface.into()]))
525+
Ok(())
539526
}
540527

541528
async fn sic_create_network_interfaces_undo(
542529
sagactx: ActionContext<SagaInstanceCreate>,
543530
) -> Result<(), anyhow::Error> {
544-
// We issue a request to delete any interfaces associated with this
545-
// instance. In the case we failed partway through allocating interfaces,
546-
// we won't have cached the interface records in the saga log, but they're
547-
// definitely still in the database. Just delete every interface that
548-
// exists, even if there are zero such records.
531+
// We issue a request to delete any interfaces associated with this instance.
532+
// In the case we failed partway through allocating interfaces, we need to
533+
// clean up any previously-created interface records from the database.
534+
// Just delete every interface that exists, even if there are zero such
535+
// records.
549536
let osagactx = sagactx.user_data();
550537
let datastore = osagactx.datastore();
551538
let saga_params = sagactx.saga_params();

0 commit comments

Comments
 (0)