|
| 1 | +# Copyright 2022 The Regents of the University of California (Regents) |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | +# |
| 15 | +# Copyright ©2022. The Regents of the University of California (Regents). |
| 16 | +# All Rights Reserved. Permission to use, copy, modify, and distribute this |
| 17 | +# software and its documentation for educational, research, and not-for-profit |
| 18 | +# purposes, without fee and without a signed licensing agreement, is hereby |
| 19 | +# granted, provided that the above copyright notice, this paragraph and the |
| 20 | +# following two paragraphs appear in all copies, modifications, and |
| 21 | +# distributions. Contact The Office of Technology Licensing, UC Berkeley, 2150 |
| 22 | +# Shattuck Avenue, Suite 510, Berkeley, CA 94720-1620, (510) 643-7201, |
| 23 | +# [email protected], http://ipira.berkeley.edu/industry-info for commercial |
| 24 | +# licensing opportunities. IN NO EVEpNT SHALL REGENTS BE LIABLE TO ANY PARTY |
| 25 | +# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, |
| 26 | +# INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS |
| 27 | +# DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| 28 | +# DAMAGE. REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT |
| 29 | +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
| 30 | +# PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, |
| 31 | +# PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE |
| 32 | +# MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| 33 | + |
| 34 | +import json |
| 35 | +import os |
| 36 | + |
| 37 | +import subprocess |
| 38 | +import uuid |
| 39 | + |
| 40 | +from .cloud_instance import CloudInstance |
| 41 | + |
| 42 | +from .util import extract_bash_column |
| 43 | + |
| 44 | + |
| 45 | +class GCPCloudInstance(CloudInstance): |
| 46 | + """GCP Implementation of CloudInstance.""" |
| 47 | + |
| 48 | + def __init__( |
| 49 | + self, |
| 50 | + project_id, |
| 51 | + ami_image='projects/ubuntu-os-cloud/global/images/ubuntu-2204-jammy-v20220712a', |
| 52 | + zone="us-central1-a", |
| 53 | + machine_type="e2-medium", |
| 54 | + disk_size=10, |
| 55 | + **kwargs, |
| 56 | + ): |
| 57 | + super().__init__(**kwargs) |
| 58 | + self.cloud_service_provider = "GCP" |
| 59 | + |
| 60 | + id_ = str(uuid.uuid4())[0:8] |
| 61 | + self._name = f'fog-{id_}-{self._name}' |
| 62 | + |
| 63 | + self.zone = zone |
| 64 | + self.type = machine_type |
| 65 | + self.compute_instance_disk_size = disk_size # GB |
| 66 | + self.gcp_ami_image = ami_image |
| 67 | + |
| 68 | + self._working_dir = os.path.join(self._working_dir_base, self._name) |
| 69 | + os.makedirs(self._working_dir, exist_ok=True) |
| 70 | + |
| 71 | + self._project_id = project_id |
| 72 | + |
| 73 | + # after config |
| 74 | + self._ssh_key = None |
| 75 | + |
| 76 | + self.create() |
| 77 | + |
| 78 | + def create(self): |
| 79 | + self.logger.info(f"Creating new GCP Compute Engine instance with name {self._name}") |
| 80 | + self.create_compute_engine_instance() |
| 81 | + self.info(flush_to_disk=True) |
| 82 | + self.connect() |
| 83 | + self.install_ros() |
| 84 | + self.install_colcon() |
| 85 | + self.install_cloud_dependencies() |
| 86 | + self.push_ros_workspace() |
| 87 | + self.info(flush_to_disk=True) |
| 88 | + self._is_created = True |
| 89 | + |
| 90 | + def info(self, flush_to_disk=True): |
| 91 | + info_dict = super().info(flush_to_disk) |
| 92 | + info_dict["compute_region"] = self.zone |
| 93 | + info_dict["compute_instance_type"] = self.type |
| 94 | + info_dict["disk_size"] = self.compute_instance_disk_size |
| 95 | + info_dict["compute_instance_id"] = self._name |
| 96 | + if flush_to_disk: |
| 97 | + with open(os.path.join(self._working_dir, "info"), "w+") as f: |
| 98 | + json.dump(info_dict, f) |
| 99 | + return info_dict |
| 100 | + |
| 101 | + def create_compute_engine_instance(self): |
| 102 | + os.system(f'gcloud config set project {self._project_id}') |
| 103 | + |
| 104 | + result = subprocess.check_output(f'gcloud compute instances create {self._name} ' |
| 105 | + f'--project={self._project_id} --zone={self.zone} --machine-type={self.type} ' |
| 106 | + '--network-interface=network-tier=PREMIUM,subnet=default ' |
| 107 | + '--maintenance-policy=MIGRATE --provisioning-model=STANDARD ' |
| 108 | + '--scopes=https://www.googleapis.com/auth/devstorage.read_only,' |
| 109 | + 'https://www.googleapis.com/auth/logging.write,' |
| 110 | + 'https://www.googleapis.com/auth/monitoring.write,' |
| 111 | + 'https://www.googleapis.com/auth/servicecontrol,' |
| 112 | + 'https://www.googleapis.com/auth/service.management.readonly,' |
| 113 | + 'https://www.googleapis.com/auth/trace.append ' |
| 114 | + '--create-disk=auto-delete=yes,' |
| 115 | + 'boot=yes,' |
| 116 | + f'device-name={self._name},' |
| 117 | + f'image={self.gcp_ami_image},' |
| 118 | + 'mode=rw,' |
| 119 | + f'size={self.compute_instance_disk_size},' |
| 120 | + f'type=projects/{self._project_id}/zones/{self.zone}/diskTypes/pd-balanced ' |
| 121 | + '--no-shielded-secure-boot ' |
| 122 | + '--shielded-vtpm ' |
| 123 | + '--shielded-integrity-monitoring ' |
| 124 | + '--reservation-affinity=any', shell=True).decode() |
| 125 | + |
| 126 | + # Grab external IP |
| 127 | + ip = extract_bash_column(result, 'EXTERNAL_IP') |
| 128 | + |
| 129 | + # Verifies the response was an ip |
| 130 | + if len(ip.split('.')) != 4: |
| 131 | + raise Exception(f'Error creating instance: {ip}') |
| 132 | + |
| 133 | + self._ip = ip |
| 134 | + |
| 135 | + # Generate SSH keys |
| 136 | + os.system(f"printf '\n\n' | gcloud compute ssh {self._name} --zone {self.zone}") |
| 137 | + |
| 138 | + user = subprocess.check_output('whoami', shell=True).decode().strip() |
| 139 | + |
| 140 | + # Username |
| 141 | + self._username = (open(f'/home/{user}/.ssh/google_compute_engine.pub'). |
| 142 | + read()).split(' ')[-1].strip().split('@')[0] |
| 143 | + |
| 144 | + self._ssh_key_path = f'/home/{user}/.ssh/google_compute_engine' |
| 145 | + self._is_created = True |
| 146 | + |
| 147 | + self.logger.info( |
| 148 | + f"Created {self.type} instance named {self._name} " |
| 149 | + f"with id {self._name} and public IP address {self._ip}" |
| 150 | + ) |
0 commit comments