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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
include cf_remote/nt-discovery.sh
include cf_remote/Vagrantfile
64 changes: 64 additions & 0 deletions cf_remote/Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'json'

config_path = File.expand_path("../config.json", __FILE__)
if !File.file?(config_path)
abort "Need a config file for this host at " + config_path
end
config = JSON.parse(File.read(config_path))

VM_BOX = config['box']
VM_COUNT = config['count']
VM_MEMORY = config['memory']
VM_CPUS = config['cpus']
VM_PROVISION = File.expand_path(config['provision'], __FILE__)
VM_NAME = config['name']
SYNC_FOLDER = config['sync_folder']

Vagrant.configure("2") do |config|

(1..VM_COUNT).each do |i|

config.vm.define "#{VM_NAME}-#{i}" do |node|
node.vm.hostname = "#{VM_NAME}-#{i}"
node.vm.network "private_network", ip: "192.168.56.#{9 + i}"
node.vm.box = VM_BOX

node.vm.provision "shell" do |s|
ssh_pub_key = File.readlines("#{Dir.home}/.ssh/id_rsa.pub").first.strip
s.inline = <<-SHELL
echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys
echo #{ssh_pub_key} >> /root/.ssh/authorized_keys
SHELL
end
node.vm.provision "bootstrap", type: "shell", path: VM_PROVISION
node.vm.synced_folder ".", "/vagrant",
rsync__args: ["--verbose", "--archive", "--delete", "-z", "--links"]
node.vm.synced_folder "#{SYNC_FOLDER}", "/synched_folder",
rsync__args: ["--verbose", "--archive", "--delete", "-z", "--links"]

# https://bugs.launchpad.net/cloud-images/+bug/1874453
NOW = Time.now.strftime("%d.%m.%Y.%H:%M:%S")
FILENAME = "serial-debug-%s.log" % NOW
node.vm.provider "virtualbox" do |vb|
vb.memory = VM_MEMORY
vb.cpus = VM_CPUS
vb.customize [ "guestproperty", "set", :id, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", 1000 ]
Copy link
Contributor

Choose a reason for hiding this comment

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

regarding time and dns you might need to include this snippet from jenkins-vms that handles darwin/macos

https://gitlab.com/Northern.tech/CFEngine/jenkins-vms/-/blob/master/scripts/common.rb?ref_type=heads#L104

vb.customize [ "modifyvm", :id, "--uart1", "0x3F8", "4" ]
vb.customize [ "modifyvm", :id, "--uartmode1", "file",
Comment on lines +48 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

what are these doing in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is to fix the bug mentioned in the ticket in the comment above (ubuntu/focal64 very slow to boot and reboots once ) . I got that from the Vagrantfile in starter_pack

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably a good idea to mention why they are there. A ticket at vagrant would be most awesome.

File.join(Dir.pwd, FILENAME) ]
end

node.vm.provider :libvirt do |v, override|
v.memory = VM_MEMORY
v.cpus = VM_CPUS
# Fedora 30+ uses QEMU sessions by default, breaking pretty much all
# previously working Vagrantfiles:
# https://fedoraproject.org/wiki/Changes/Vagrant_2.2_with_QEMU_Session#Upgrade.2Fcompatibility_impact
v.qemu_use_session = false
override.vm.synced_folder "#{SYNC_FOLDER}", "/synched_folder", type: :rsync
end
end
end
end
6 changes: 5 additions & 1 deletion cf_remote/aramid.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import time
from urllib.parse import urlparse
from cf_remote import log
from cf_remote.paths import SSH_CONFIG_FPATH

DEFAULT_SSH_ARGS = [
"-oLogLevel=ERROR",
Expand All @@ -37,6 +38,8 @@
"-oBatchMode=yes",
"-oHostKeyAlgorithms=+ssh-rsa",
"-oPubkeyAcceptedKeyTypes=+ssh-rsa",
"-F",
"{}".format(SSH_CONFIG_FPATH),
]
"""Default arguments to use with all SSH commands (incl. 'scp' and 'rsync')"""

Expand Down Expand Up @@ -338,10 +341,11 @@ def execute(
if host.port != _DEFAULT_SSH_PORT:
port_args += ["-p", str(host.port)]
proc = subprocess.Popen(
["ssh", host.login]
["ssh"]
+ DEFAULT_SSH_ARGS
+ port_args
+ host.extra_ssh_args
+ [host.login]
+ [commands[i]],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Expand Down
156 changes: 91 additions & 65 deletions cf_remote/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
from cf_remote.web import download_package
from cf_remote.paths import (
cf_remote_dir,
cf_remote_packages_dir,
CLOUD_CONFIG_FPATH,
CLOUD_STATE_FPATH,
cf_remote_packages_dir,
SSH_CONFIG_FPATH,
SSH_CONFIGS_JSON_FPATH,
)
from cf_remote.utils import (
copy_file,
Expand All @@ -37,7 +39,14 @@
CFRChecksumError,
CFRUserError,
)
from cf_remote.spawn import VM, VMRequest, Providers, AWSCredentials, GCPCredentials
from cf_remote.spawn import (
CloudVM,
VMRequest,
Providers,
AWSCredentials,
GCPCredentials,
VagrantVM,
)
from cf_remote.spawn import spawn_vms, destroy_vms, dump_vms_info, get_cloud_driver
from cf_remote import log
from cf_remote import cloud_data
Expand Down Expand Up @@ -393,6 +402,9 @@ def spawn(
network=None,
public_ip=True,
extend_group=False,
vagrant_cpus=None,
vagrant_sync=None,
vagrant_provision=None,
):
creds_data = None
if os.path.exists(CLOUD_CONFIG_FPATH):
Expand Down Expand Up @@ -469,13 +481,20 @@ def spawn(
network=network,
role=role,
spawned_cb=print_progress_dot,
vagrant_cpus=vagrant_cpus,
vagrant_sync=vagrant_sync,
vagrant_provision=vagrant_provision,
)
except ValueError as e:
print("\nError: Failed to spawn VMs - " + str(e))
return 1
print("DONE")

if public_ip and (not all(vm.public_ips for vm in vms)):
if (
provider != Providers.VAGRANT
and public_ip
and (not all(vm.public_ips for vm in vms))
):
print("Waiting for VMs to get IP addresses...", end="")
sys.stdout.flush() # STDOUT is line-buffered
while not all(vm.public_ips for vm in vms):
Expand All @@ -488,6 +507,16 @@ def spawn(
else:
vms_info[group_key] = dump_vms_info(vms)

if provider == Providers.VAGRANT:
vmdir = vms[0].vmdir
ssh_config = read_json(SSH_CONFIGS_JSON_FPATH)
if not ssh_config:
ssh_config = {}

with open(os.path.join(vmdir, "vagrant-ssh-config"), "r") as f:
ssh_config[group_key] = f.read()
write_json(SSH_CONFIGS_JSON_FPATH, ssh_config)

write_json(CLOUD_STATE_FPATH, vms_info)
print("Details about the spawned VMs can be found in %s" % CLOUD_STATE_FPATH)

Expand All @@ -510,6 +539,36 @@ def _delete_saved_group(vms_info, group_name):
del vms_info[group_name]


def _get_cloud_vms(provider, creds, region, group):
if creds is None:
raise CFRExitError("Missing/incomplete {} credentials".format(provider.upper()))
driver = get_cloud_driver(provider, creds, region)

assert driver is not None

nodes = driver.list_nodes()
for name, vm_info in group.items():
if name == "meta":
continue
vm_uuid = vm_info["uuid"]
vm = CloudVM.get_by_uuid(vm_uuid, nodes=nodes)
if vm is not None:
yield vm
else:
print("VM '%s' not found in the clouds" % vm_uuid)


def _get_vagrant_vms(group):
for name, vm_info in group.items():
if name == "meta":
continue
vm = VagrantVM.get_by_info(name, vm_info)
if vm is not None:
yield vm
else:
print("VM '%s' not found locally" % name)


def destroy(group_name=None):
if os.path.exists(CLOUD_CONFIG_FPATH):
creds_data = read_json(CLOUD_CONFIG_FPATH)
Expand Down Expand Up @@ -549,92 +608,54 @@ def destroy(group_name=None):
raise CFRUserError("No saved VMs found in '{}'".format(CLOUD_STATE_FPATH))

to_destroy = []
group_names = None
if group_name:
if not group_name.startswith("@"):
group_name = "@" + group_name
if group_name not in vms_info:
print("Group '%s' not found" % group_name)
return 1

group_names = [group_name]
else:
group_names = [key for key in vms_info.keys() if key.startswith("@")]

ssh_config = read_json(SSH_CONFIGS_JSON_FPATH)
assert group_names is not None
for group_name in group_names:
if _is_saved_group(vms_info, group_name):
_delete_saved_group(vms_info, group_name)
write_json(CLOUD_STATE_FPATH, vms_info)
return 0

print("Destroying hosts in the '%s' group" % group_name)
continue

region = vms_info[group_name]["meta"]["region"]
provider = vms_info[group_name]["meta"]["provider"]
if provider not in ["aws", "gcp"]:
if provider not in ["aws", "gcp", "vagrant"]:
raise CFRUserError(
"Unsupported provider '{}' encountered in '{}', only aws / gcp is supported".format(
"Unsupported provider '{}' encountered in '{}', only aws, gcp and vagrant are supported".format(
provider, CLOUD_STATE_FPATH
)
)

driver = None
group = vms_info[group_name]
vms = []
if provider == "aws":
if aws_creds is None:
raise CFRExitError("Missing/incomplete AWS credentials")
driver = get_cloud_driver(Providers.AWS, aws_creds, region)
vms = _get_cloud_vms(Providers.AWS, aws_creds, region, group)
if provider == "gcp":
if gcp_creds is None:
raise CFRExitError("Missing/incomplete GCP credentials")
driver = get_cloud_driver(Providers.GCP, gcp_creds, region)
assert driver is not None
vms = _get_cloud_vms(Providers.GCP, gcp_creds, region, group)
if provider == "vagrant":
vms = _get_vagrant_vms(group)

nodes = driver.list_nodes()
for name, vm_info in vms_info[group_name].items():
if name == "meta":
continue
vm_uuid = vm_info["uuid"]
vm = VM.get_by_uuid(vm_uuid, nodes=nodes)
if vm is not None:
to_destroy.append(vm)
else:
print("VM '%s' not found in the clouds" % vm_uuid)
del vms_info[group_name]
else:
print("Destroying all hosts")
for group_name in [key for key in vms_info.keys() if key.startswith("@")]:
if _is_saved_group(vms_info, group_name):
_delete_saved_group(vms_info, group_name)
continue
for vm in vms:
to_destroy.append(vm)

region = vms_info[group_name]["meta"]["region"]
provider = vms_info[group_name]["meta"]["provider"]
if provider not in ["aws", "gcp"]:
raise CFRUserError(
"Unsupported provider '{}' encountered in '{}', only aws / gcp is supported".format(
provider, CLOUD_STATE_FPATH
)
)
del vms_info[group_name]

driver = None
if provider == "aws":
if aws_creds is None:
raise CFRExitError("Missing/incomplete AWS credentials")
driver = get_cloud_driver(Providers.AWS, aws_creds, region)
if provider == "gcp":
if gcp_creds is None:
raise CFRExitError("Missing/incomplete GCP credentials")
driver = get_cloud_driver(Providers.GCP, gcp_creds, region)
assert driver is not None

nodes = driver.list_nodes()
for name, vm_info in vms_info[group_name].items():
if name == "meta":
continue
vm_uuid = vm_info["uuid"]
vm = VM.get_by_uuid(vm_uuid, nodes=nodes)
if vm is not None:
to_destroy.append(vm)
else:
print("VM '%s' not found in the clouds" % vm_uuid)
del vms_info[group_name]
if ssh_config and group_name in ssh_config:
del ssh_config[group_name]

destroy_vms(to_destroy)
write_json(CLOUD_STATE_FPATH, vms_info)
write_json(SSH_CONFIGS_JSON_FPATH, ssh_config)
return 0


Expand All @@ -655,6 +676,11 @@ def list_platforms():
return 0


def list_boxes():
result = subprocess.run(["vagrant", "box", "list"])
return result.returncode


def init_cloud_config():
if os.path.exists(CLOUD_CONFIG_FPATH):
print("File %s already exists" % CLOUD_CONFIG_FPATH)
Expand Down Expand Up @@ -1010,7 +1036,7 @@ def connect_cmd(hosts):
raise CFRExitError("You can only connect to one host at a time")

print("Opening a SSH command shell...")
r = subprocess.run(["ssh", hosts[0]])
r = subprocess.run(["ssh", "-F", SSH_CONFIG_FPATH, hosts[0]])
if r.returncode == 0:
return 0
if r.returncode < 0:
Expand Down
Loading