Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Support #109

Open
wants to merge 29 commits into
base: humble
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
12dffee
image cli command
edithllontop1 Aug 15, 2022
d5540d4
before testing
edithllontop1 Aug 15, 2022
11d3468
finished image draft, first test
edithllontop1 Aug 24, 2022
6c80182
fixed changes made to delete file
edithllontop1 Aug 24, 2022
3587071
added image as a command to run
edithllontop1 Aug 24, 2022
741388c
printing instance
edithllontop1 Aug 24, 2022
30d298f
syntax errors
edithllontop1 Aug 25, 2022
15fcb26
working image command
edithllontop1 Aug 26, 2022
b0eb173
working image command, fixed a few print statements
edithllontop1 Aug 26, 2022
7f9169a
testing to see if delete command works
edithllontop1 Aug 26, 2022
4252ee0
makign sure shutdown works
edithllontop1 Aug 26, 2022
9d6440f
making sure shutting down instance works
edithllontop1 Aug 26, 2022
158a817
added missing counter for shutdown
edithllontop1 Aug 26, 2022
56bc28b
Working image_cli_command
SimeonOA Aug 27, 2022
8afb7ad
Changed image file to only create image and not shutdown or delete
edithllontop1 Sep 7, 2022
d47c2be
Updates for Timing Tests and ICRA Paper
SimeonOA Sep 22, 2022
bc0bb19
Merge branch 'image_cli_command' of https://github.com/BerkeleyAutoma…
SimeonOA Sep 22, 2022
aa8500b
Merge pull request #92 from BerkeleyAutomation/image_cli_command
SimeonOA Sep 22, 2022
a8fb166
Merge pull request #93 from BerkeleyAutomation/datacenter_selection
SimeonOA Oct 3, 2022
1821aa2
Merge pull request #102 from BerkeleyAutomation/ICRA2023-Review
SimeonOA Oct 17, 2022
366fcd6
added asian pacific amis and dependencies needed for generic ubuntu …
zhanhugo Oct 20, 2022
b302895
Merge pull request #104 from BerkeleyAutomation/fix_generic_ami_launch
KeplerC Oct 24, 2022
ce3a1af
k8s -> review (#94)
nikhiljha Nov 1, 2022
09b2891
Addresses #97 via vendoring
KDharmarajanDev Nov 1, 2022
5d0e82e
Merge pull request #107 from BerkeleyAutomation/ICRA2023-Review
SimeonOA Nov 1, 2022
0ff7965
Merge pull request #106 from BerkeleyAutomation/wgconfig-addition
SimeonOA Nov 7, 2022
e577558
Merge pull request #108 from BerkeleyAutomation/ICRA2023-Review
SimeonOA Nov 7, 2022
6951d80
azure cli working
KeplerC Nov 7, 2022
c71edb3
update the readme
KeplerC Nov 7, 2022
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
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ARG UBUNTU_DISTRO=jammy
ARG ROS_DISTRO=rolling
#rolling is alternative
ARG ROS_DISTRO=humble
FROM ubuntu:${UBUNTU_DISTRO}

# Set up install, set tzdata
Expand Down
3 changes: 3 additions & 0 deletions fogros2/fogros2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@
# MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

from .aws_cloud_instance import AWSCloudInstance # noqa: F401
from .gcp_cloud_instance import GCPCloudInstance
from .azure_cloud_instance import AzureCloudInstance
from .kubernetes.generic import KubeInstance
from .cloud_node import CloudNode # noqa: F401
from .launch_description import FogROSLaunchDescription # noqa: F401
6 changes: 3 additions & 3 deletions fogros2/fogros2/aws_cloud_instance.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ def create(self):
self.info(flush_to_disk=True)
self.connect()
# Uncomment out the next three lines if you are not using a custom AMI
#self.install_ros()
#self.install_colcon()
#self.install_cloud_dependencies()
self.install_ros()
self.install_cloud_dependencies()
self.install_colcon()
self.push_ros_workspace()
self.info(flush_to_disk=True)
self._is_created = True
Expand Down
135 changes: 135 additions & 0 deletions fogros2/fogros2/azure_cloud_instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Copyright 2022 The Regents of the University of California (Regents)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright ©2022. The Regents of the University of California (Regents).
# All Rights Reserved. Permission to use, copy, modify, and distribute this
# software and its documentation for educational, research, and not-for-profit
# purposes, without fee and without a signed licensing agreement, is hereby
# granted, provided that the above copyright notice, this paragraph and the
# following two paragraphs appear in all copies, modifications, and
# distributions. Contact The Office of Technology Licensing, UC Berkeley, 2150
# Shattuck Avenue, Suite 510, Berkeley, CA 94720-1620, (510) 643-7201,
# [email protected], http://ipira.berkeley.edu/industry-info for commercial
# licensing opportunities. IN NO EVEpNT SHALL REGENTS BE LIABLE TO ANY PARTY
# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
# INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
# DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE. REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY,
# PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
# MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

import json
import os

import subprocess
import uuid
import time
from .cloud_instance import CloudInstance
import json
from .util import extract_bash_column


class AzureCloudInstance(CloudInstance):
"""Azure Implementation of CloudInstance."""

def __init__(
self,
resource_group,
ami_image='UbuntuLTS',
zone="eastus",
machine_type="Standard_DS2_v2",
disk_size=10,
**kwargs,
):
super().__init__(**kwargs)
self.cloud_service_provider = "Azure"

id_ = str(uuid.uuid4())[0:8]
self._name = f'fog-{id_}-{self._name}'

self.zone = zone
self.type = machine_type
self.compute_instance_disk_size = disk_size # GB
self.azure_ami_image = ami_image

self._working_dir = os.path.join(self._working_dir_base, self._name)
os.makedirs(self._working_dir, exist_ok=True)

self.resource_group = resource_group

# after config
self._ssh_key = None

self.create()

def create(self):
self.logger.info(f"Creating new Azure Compute Engine instance with name {self._name}")
self.create_compute_engine_instance()
self.info(flush_to_disk=True)
self.connect()
self.install_ros()
self.install_colcon()
self.install_cloud_dependencies()
self.push_ros_workspace()
self.info(flush_to_disk=True)
self._is_created = True

def info(self, flush_to_disk=True):
info_dict = super().info(flush_to_disk)
info_dict["compute_region"] = self.zone
info_dict["compute_instance_type"] = self.type
info_dict["disk_size"] = self.compute_instance_disk_size
info_dict["compute_instance_id"] = self._name
if flush_to_disk:
with open(os.path.join(self._working_dir, "info"), "w+") as f:
json.dump(info_dict, f)
return info_dict

def create_compute_engine_instance(self):
os.system(f'az group create --name {self.resource_group} --location {self.zone} ')
time.sleep(1)

cmd = f'az vm create --resource-group {self.resource_group} ' + f'--name={self._name} --image={self.azure_ami_image} --machine-type={self.type} ' + f'--admin-username ubuntu ' + '--generate-ssh-keys '
print(cmd)
result = subprocess.check_output(f'az vm create --resource-group {self.resource_group} '
f'--name={self._name} --image={self.azure_ami_image} '
# f'--machine-type={self.type} '
f'--admin-username ubuntu '
'--generate-ssh-keys ', shell=True).decode()
print(result)

# Grab external IP
ip = json.loads(result)["publicIpAddress"] #extract_bash_column(result, 'publicIpAddress')

# Verifies the response was an ip
if len(ip.split('.')) != 4:
raise Exception(f'Error creating instance: {ip}')

self._ip = ip

# # Generate SSH keys
# os.system(f"printf '\n\n' | gcloud compute ssh {self._name} --zone {self.zone}")

user = subprocess.check_output('whoami', shell=True).decode().strip()


self._ssh_key_path = f'/home/{user}/.ssh/id_rsa'
self._is_created = True

self.logger.info(
f"Created {self.type} instance named {self._name} "
f"with id {self._name} and public IP address {self._ip}"
)
34 changes: 25 additions & 9 deletions fogros2/fogros2/cloud_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(
self.cyclone_builder = None
self.scp = None
self._ip = None
self._vpn_ip = None
self.ros_workspace = ros_workspace
self.ros_distro = os.getenv("ROS_DISTRO")
self.logger.debug(f"Using ROS workspace: {self.ros_workspace}")
Expand All @@ -84,6 +85,7 @@ def __init__(
self.cloud_service_provider = None
self.dockers = []
self.launch_foxglove = launch_foxglove
self._username = 'ubuntu'

@abc.abstractmethod
def create(self):
Expand All @@ -102,15 +104,23 @@ def info(self, flush_to_disk=True):
with open(os.path.join(self._working_dir, "info"), "w+") as f:
json.dump(info_dict, f)
return info_dict

def force_start_vpn(self):
return True

def connect(self):
self.scp = SCPClient(self._ip, self._ssh_key_path)
self.scp = SCPClient(self._ip, self._ssh_key_path, username=self._username)
self.scp.connect()

@property
def ip(self):
return self._ip

@property
def vpn_ip(self):
# Use this when the VPN IP is not None.
return self._vpn_ip

@property
def is_created(self):
return self._is_created
Expand All @@ -125,10 +135,14 @@ def apt_install(self, args):
)

def pip_install(self, args):
self.scp.execute_cmd(f"sudo pip3 install {args}")
self.scp.execute_cmd(f"python3 -m pip install {args}")

def install_cloud_dependencies(self):
self.apt_install("wireguard unzip docker.io python3-pip ros-humble-rmw-cyclonedds-cpp")
self.pip_install("boto3")
self.pip_install("paramiko")
self.pip_install("scp")
self.pip_install("wgconfig")

def install_ros(self):
# setup sources
Expand Down Expand Up @@ -159,7 +173,7 @@ def install_ros(self):
self.scp.execute_cmd("export LANG=en_US.UTF-8")

# install ros2 packages
# self.apt_install(f"ros-{self.ros_distro}-desktop")
self.apt_install(f"ros-{self.ros_distro}-desktop")

# source environment
self.scp.execute_cmd(f"source /opt/ros/{self.ros_distro}/setup.bash")
Expand All @@ -173,7 +187,7 @@ def configure_rosbridge(self):
rosbridge_launch_script = (
"ssh -o StrictHostKeyChecking=no -i "
f"{self._ssh_key_path}"
" ubuntu@"
f" {self._username}@"
f"{self._ip}"
f' "source /opt/ros/{self.ros_distro}/setup.bash && '
'ros2 launch rosbridge_server rosbridge_websocket_launch.xml &"'
Expand Down Expand Up @@ -203,8 +217,10 @@ def push_ros_workspace(self):
make_zip_file(workspace_path, zip_dst)
self.scp.execute_cmd("echo removing old workspace")
self.scp.execute_cmd("rm -rf ros_workspace.zip ros2_ws fog_ws")
self.scp.send_file(f"{zip_dst}.zip", "/home/ubuntu/")
self.scp.execute_cmd("unzip -q /home/ubuntu/ros_workspace.zip")
#self.scp.send_file(f"{zip_dst}.zip", "/home/ubuntu/")
self.scp.send_file(f"{zip_dst}.tar", "/home/ubuntu/")
#self.scp.execute_cmd("unzip -q /home/ubuntu/ros_workspace.zip")
self.scp.execute_cmd("tar -xf /home/ubuntu/ros_workspace.tar")
self.scp.execute_cmd("echo successfully extracted new workspace")

def push_to_cloud_nodes(self):
Expand All @@ -223,17 +239,17 @@ def push_and_setup_vpn(self):

def configure_DDS(self):
# configure DDS
self.cyclone_builder = CycloneConfigBuilder(["10.0.0.1"])
self.cyclone_builder = CycloneConfigBuilder(["10.0.0.1"], username=self._username)
self.cyclone_builder.generate_config_file()
self.scp.send_file("/tmp/cyclonedds.xml", "~/cyclonedds.xml")

def launch_cloud_node(self):
cmd_builder = BashBuilder()
cmd_builder.append(f"source /opt/ros/{self.ros_distro}/setup.bash")
cmd_builder.append(
"cd /home/ubuntu/fog_ws && colcon build --cmake-clean-cache"
f"cd /home/{self._username}/fog_ws && colcon build --cmake-clean-cache"
)
cmd_builder.append(". /home/ubuntu/fog_ws/install/setup.bash")
cmd_builder.append(f". /home/{self._username}/fog_ws/install/setup.bash")
cmd_builder.append(self.cyclone_builder.env_cmd)
ros_domain_id = os.environ.get("ROS_DOMAIN_ID")
if not ros_domain_id:
Expand Down
19 changes: 8 additions & 11 deletions fogros2/fogros2/dds_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,18 @@ def generate_config_file(self):


class CycloneConfigBuilder(DDSConfigBuilder):
def __init__(self, ip_addresses):
def __init__(self, ip_addresses, username='ubuntu'):
super().__init__(ip_addresses)
self.config_save_path = "/tmp/cyclonedds.xml"
self.env_cmd = (
"export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp && "
"export CYCLONEDDS_URI=file:///home/ubuntu/cyclonedds.xml"
f"export CYCLONEDDS_URI=file:///home/{username}/cyclonedds.xml"
)

def generate_config_file(self):
if ubuntu_release == "20.04":
interfaces = """
<NetworkInterfaceAddress>wg0</NetworkInterfaceAddress>
<AllowMulticast>false</AllowMulticast>
"""
else:
interfaces = """
def generate_config_file(self, extra_peers = []):
interfaces = """
<Interfaces>
<NetworkInterface name="wg0"/>
<NetworkInterface name="wg0" />
</Interfaces>
"""

Expand All @@ -82,6 +76,8 @@ def generate_config_file(self):
'cyclonedds/master/etc/cyclonedds.xsd"'
)

peer_xml = "".join(f"<Peer address=\"{peer}\"/>\n" for peer in extra_peers)

template = f"""
<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS {xmlvals}>
Expand All @@ -91,6 +87,7 @@ def generate_config_file(self):
<Peers>
<Peer address="10.0.0.1"/>
<Peer address="10.0.0.2"/>
{peer_xml}
</Peers>
<ParticipantIndex>auto</ParticipantIndex>
</Discovery>
Expand Down
Loading