Skip to content

Commit 7602a47

Browse files
authored
Add files via upload
1 parent 3a3e6f3 commit 7602a47

20 files changed

Lines changed: 1809 additions & 0 deletions
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,234 @@
1+
# Iceberg Table Migration to OCI
12

3+
This repository is a Terraform-based demo for migrating an Apache Iceberg table into OCI Object Storage and validating it with Spark.
4+
5+
Current primary run mode:
6+
7+
```text
8+
local terminal -> terraform apply -> SSH to OCI VM -> run /opt/iceberg scripts
9+
```
10+
11+
## What It Does
12+
13+
Terraform provisions:
14+
15+
- OCI network
16+
- OCI compute VM
17+
- OCI Object Storage bucket
18+
- cloud-init bootstrap on the VM
19+
20+
Cloud-init installs and prepares:
21+
22+
- Docker
23+
- Spark
24+
- Apache Iceberg runtime
25+
- PostgreSQL
26+
- Hive Metastore container
27+
- Iceberg JDBC catalog
28+
- OCI CLI and AWS CLI
29+
- helper scripts under `/opt/iceberg`
30+
31+
The demo scripts then:
32+
33+
1. Generate a real tiny Iceberg table using MinIO as fake AWS S3.
34+
2. Export real Iceberg `data/` and `metadata/` files.
35+
3. Copy those files into OCI Object Storage.
36+
4. Register the copied table in Iceberg JDBC catalog.
37+
5. Validate with Spark.
38+
6. Optionally validate with Trino.
39+
40+
## Terraform Options
41+
42+
Optional Trino validation is controlled by the Terraform variable `validation_engines`.
43+
44+
Set it in `terraform.tfvars` before `terraform apply`:
45+
46+
```hcl
47+
validation_engines = ["spark", "trino"]
48+
```
49+
50+
Default is Spark only:
51+
52+
```hcl
53+
validation_engines = ["spark"]
54+
```
55+
56+
The included `terraform.tfvars` may already set `["spark", "trino"]`; change it to `["spark"]` if you want Spark-only validation.
57+
58+
You can also override it for one VM run:
59+
60+
```bash
61+
export VALIDATION_ENGINES="spark,trino"
62+
```
63+
64+
## Configure Terraform Inputs
65+
66+
Populate these values before running Terraform.
67+
68+
In `provider.auto.tfvars`:
69+
70+
- `provider_oci.tenancy_ocid`: tenancy OCID from OCI.
71+
- `provider_oci.user_ocid`: user OCID for the API key owner.
72+
- `provider_oci.fingerprint`: fingerprint of the uploaded OCI API key.
73+
- `provider_oci.private_key_path`: local path to the matching API private key.
74+
- `provider_oci.private_key_password`: private key passphrase, or `""` if the key has none.
75+
- `provider_oci.region`: target OCI region, for example `eu-frankfurt-1`.
76+
- `compartment_ids.sandbox`: compartment OCID where Terraform creates the resources.
77+
78+
In `terraform.tfvars`:
79+
80+
- `linux_images`: Oracle Linux 9 image OCID for the selected `provider_oci.region`.
81+
- `instance_params`: VM availability domain, shape, subnet, image version, OCPUs, and memory.
82+
- `ssh_public_key`, which must point to the public key Terraform should inject into the VM.
83+
- `registry`: OCIR region key, for example `fra.ocir.io` or `iad.ocir.io`.
84+
- `bucket_params`: Object Storage bucket name, compartment name, storage tier, and optional `force_destroy`.
85+
- Network maps: adjust CIDRs, subnet privacy, route rules, and security list rules if the defaults do not fit your tenancy.
86+
- `validation_engines`: use `["spark"]` for Spark only or `["spark", "trino"]` to add Trino validation.
87+
88+
## Quick Run
89+
90+
From this repository:
91+
92+
```bash
93+
terraform init
94+
terraform validate
95+
terraform plan
96+
terraform apply
97+
```
98+
99+
When Terraform asks for confirmation, enter `yes`.
100+
101+
After Terraform finishes, note the public IP address from the `linux_instances` output.
102+
103+
## Post-Deploy Actions
104+
105+
SSH to the compute VM:
106+
107+
```bash
108+
ssh opc@<vm_public_ip>
109+
```
110+
111+
Confirm cloud-init completed and the helper scripts exist:
112+
113+
```bash
114+
cloud-init status --wait --long
115+
docker version
116+
/opt/spark/bin/spark-submit --version
117+
ls -l /opt/iceberg/
118+
```
119+
120+
If the helper scripts are missing, inspect the cloud-init log:
121+
122+
```bash
123+
sudo tail -n 100 /var/log/cloud-init-output.log
124+
```
125+
126+
Generate the simulated AWS Iceberg source table:
127+
128+
```bash
129+
/opt/iceberg/generate-simulated-aws-iceberg-table.sh
130+
```
131+
132+
The default export location is:
133+
134+
```text
135+
/opt/iceberg/generated_aws_source/iceberg-table-demo/lakehouse/sales/orders/
136+
```
137+
138+
Confirm the export contains Iceberg `data/` and `metadata/` files:
139+
140+
```bash
141+
find /opt/iceberg/generated_aws_source/iceberg-table-demo/lakehouse/sales/orders -type f | sort
142+
find /opt/iceberg/generated_aws_source/iceberg-table-demo/lakehouse/sales/orders/metadata -name "*.metadata.json" -type f | sort
143+
```
144+
145+
Copy the generated Iceberg files to OCI Object Storage:
146+
147+
```bash
148+
/opt/iceberg/copy-simulated-source-to-oci.sh
149+
```
150+
151+
Verify the copied objects:
152+
153+
```bash
154+
oci os object list \
155+
--auth instance_principal \
156+
--bucket-name iceberg-table-demo \
157+
--prefix lakehouse/sales/orders/ \
158+
--fields name \
159+
--all
160+
```
161+
162+
If you changed the bucket or table prefix, use the same `BUCKET` and `TABLE_PREFIX` values for generation, copy, verification, and registration.
163+
164+
Create an OCI Customer Secret Key for S3-compatible Object Storage access, then set it on the VM. Use the Customer Secret Key access key as `OCI_ACCESS_KEY_ID` and the generated secret value as `OCI_SECRET_ACCESS_KEY`.
165+
166+
```bash
167+
export OCI_ACCESS_KEY_ID="<access-key>"
168+
export OCI_SECRET_ACCESS_KEY="<secret-key>"
169+
```
170+
171+
The VM scripts use the Terraform region by default. Set `OCI_REGION` or `OCI_S3_ENDPOINT` only if you need to override the generated endpoint:
172+
173+
```bash
174+
export OCI_REGION="<oci_region>"
175+
export OCI_S3_ENDPOINT="https://<namespace>.compat.objectstorage.<oci_region>.oci.customer-oci.com"
176+
```
177+
178+
Register the copied table and validate it with Spark:
179+
180+
```bash
181+
/opt/iceberg/register-simulated-oci-table.sh
182+
```
183+
184+
If `validation_engines` includes `trino`, the same registration script also runs Trino validation. You can enable it for one run with:
185+
186+
```bash
187+
export VALIDATION_ENGINES="spark,trino"
188+
/opt/iceberg/register-simulated-oci-table.sh
189+
```
190+
191+
To run a manual Spark SQL check:
192+
193+
```bash
194+
/opt/iceberg/spark-sql-oci.sh
195+
```
196+
197+
```sql
198+
SHOW TABLES IN oci.sales;
199+
DESCRIBE oci.sales.orders;
200+
SELECT * FROM oci.sales.orders;
201+
exit;
202+
```
203+
204+
## Input Modes
205+
206+
The scripts support two source modes:
207+
208+
| Mode | Status | How to use |
209+
| --- | --- | --- |
210+
| `simulated_aws` / MinIO | Proven default | Run `generate-simulated-aws-iceberg-table.sh`, then copy/register/validate. |
211+
| `local_export` | Supported as local copy input | Set `SOURCE_DIR` to an existing local Iceberg table export, then run the copy/register flow. |
212+
213+
Inputs must be generated by Iceberg or come from a real exported Iceberg table folder.
214+
215+
### Local Export
216+
217+
The local export folder should contain real Iceberg files:
218+
219+
```text
220+
/path/to/exported/iceberg/table/
221+
data/
222+
metadata/
223+
*.metadata.json
224+
*.avro
225+
```
226+
227+
Copy it to OCI with:
228+
229+
```bash
230+
SOURCE_DIR=/path/to/exported/iceberg/table \
231+
BUCKET=iceberg-table-demo \
232+
TABLE_PREFIX=lakehouse/sales/orders \
233+
/opt/iceberg/copy-simulated-source-to-oci.sh
234+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
2+
# The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
4+
provider "oci" {
5+
tenancy_ocid = var.provider_oci.tenancy_ocid
6+
user_ocid = var.provider_oci.user_ocid
7+
fingerprint = var.provider_oci.fingerprint
8+
private_key_path = var.provider_oci.private_key_path
9+
region = var.provider_oci.region
10+
}
11+
12+
module "network" {
13+
source = "./modules/network"
14+
vcn_params = var.vcn_params
15+
compartment_ids = var.compartment_ids
16+
igw_params = var.igw_params
17+
ngw_params = var.ngw_params
18+
rt_params = var.rt_params
19+
sl_params = var.sl_params
20+
subnet_params = var.subnet_params
21+
}
22+
23+
module "compute" {
24+
source = "./modules/instances"
25+
compartment_ids = var.compartment_ids
26+
subnet_ids = module.network.subnets_ids
27+
instance_params = var.instance_params
28+
region = var.provider_oci.region
29+
linux_images = var.linux_images
30+
ssh_public_key = var.ssh_public_key
31+
registry = var.registry
32+
validation_engines = var.validation_engines
33+
}
34+
35+
module "object-storage" {
36+
source = "./modules/object-storage"
37+
compartments = var.compartment_ids
38+
bucket_params = var.bucket_params
39+
oci_provider = var.provider_oci
40+
kms_key_ids = var.kms_key_ids
41+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
2+
# The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
4+
locals {
5+
linux_boot_volumes = [for instance in var.instance_params : instance.boot_volume_size >= 50 ? null : file(format("\n\nERROR: The boot volume size for linux instance %s is less than 50GB which is not permitted. Please add a boot volume size of 50GB or more", instance.hostname))]
6+
block_performance = {
7+
Low = "0"
8+
Balanced = "10"
9+
High = "20"
10+
}
11+
}
12+
13+
data "oci_identity_availability_domains" "ads" {
14+
compartment_id = var.compartment_ids[var.instance_params[keys(var.instance_params)[0]].compartment_name]
15+
}
16+
17+
18+
resource "oci_core_instance" "this" {
19+
for_each = var.instance_params
20+
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[each.value.ad - 1].name
21+
compartment_id = var.compartment_ids[each.value.compartment_name]
22+
shape = each.value.shape
23+
shape_config {
24+
memory_in_gbs = each.value.memory_in_gbs
25+
ocpus = each.value.ocpus
26+
}
27+
display_name = each.value.hostname
28+
preserve_boot_volume = each.value.preserve_boot_volume
29+
freeform_tags = each.value.freeform_tags
30+
is_pv_encryption_in_transit_enabled = each.value.encrypt_in_transit
31+
fault_domain = format("FAULT-DOMAIN-%s", each.value.fd)
32+
33+
34+
create_vnic_details {
35+
assign_public_ip = each.value.assign_public_ip
36+
subnet_id = var.subnet_ids[each.value.subnet_name]
37+
hostname_label = replace(lower(each.value.hostname), "_", "-")
38+
}
39+
40+
source_details {
41+
boot_volume_size_in_gbs = each.value.boot_volume_size
42+
source_type = "image"
43+
source_id = var.linux_images[var.region][each.value.image_version]
44+
}
45+
46+
metadata = {
47+
ssh_authorized_keys = file(var.ssh_public_key)
48+
user_data = base64encode(templatefile("${path.module}/../../userdata/cloudinit.sh.tftpl", {
49+
registry = var.registry
50+
region = var.region
51+
validation_engines = join(",", var.validation_engines)
52+
}))
53+
}
54+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
2+
# The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
4+
locals {
5+
linux_instances = {
6+
for instance in oci_core_instance.this :
7+
instance.display_name => { "id" : instance.id, "ip" : instance.public_ip != "" ? instance.public_ip : instance.private_ip }
8+
}
9+
linux_ids = {
10+
for instance in oci_core_instance.this :
11+
instance.display_name => instance.id
12+
}
13+
14+
linux_private_ips = {
15+
for instance in oci_core_instance.this :
16+
instance.display_name => instance.private_ip
17+
}
18+
19+
20+
all_instances = merge(local.linux_ids /*,local.windows_ids*/)
21+
all_private_ips = merge(local.linux_private_ips /*, local.windows_private_ips*/)
22+
}
23+
24+
output "linux_instances" {
25+
value = local.linux_instances
26+
}
27+
28+
output "all_instances" {
29+
value = local.all_instances
30+
}
31+
32+
output "all_private_ips" {
33+
value = local.all_private_ips
34+
}

0 commit comments

Comments
 (0)