Add Orchestrator class
* step 1: create servers and wait for them to be ready * step 2: setup chefserver * step 3-5: marked as TODO functions Plus: * 80-char limited bin/*.sh * add utils and cmd for executing command (both local and ssh)
This commit is contained in:
parent
6471d7d9f3
commit
be9ed42bcc
|
@ -5,5 +5,5 @@ mkdir -p ~/.chef
|
|||
sudo cp /etc/chef/validation.pem /etc/chef/webui.pem ~/.chef
|
||||
sudo chown -R $USER ~/.chef
|
||||
|
||||
knife configure -i -u chefroot -r ~/chef-repo --admin-client-key ~/.chef/webui.pem --validation-key ~/.chef/validation.pem --defaults
|
||||
|
||||
knife configure -i -u chefroot -r ~/chef-repo --admin-client-key \
|
||||
~/.chef/webui.pem --validation-key ~/.chef/validation.pem --defaults
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
CHEF_SERVER=$(hostname)
|
||||
CHEF_PASSWORD=${CHEF_PASSWORD:-ChefServer}
|
||||
|
||||
echo "deb http://apt.opscode.com/ `lsb_release -cs`-0.10 main" | sudo tee /etc/apt/sources.list.d/opscode.list
|
||||
echo "deb http://apt.opscode.com/ `lsb_release -cs`-0.10 main" | \
|
||||
sudo tee /etc/apt/sources.list.d/opscode.list
|
||||
|
||||
sudo mkdir -p /etc/apt/trusted.gpg.d
|
||||
gpg --keyserver keys.gnupg.net --recv-keys 83EF826A
|
||||
gpg --export packages@opscode.com | sudo tee /etc/apt/trusted.gpg.d/opscode-keyring.gpg > /dev/null
|
||||
gpg --export packages@opscode.com | \
|
||||
sudo tee /etc/apt/trusted.gpg.d/opscode-keyring.gpg > /dev/null
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
|
@ -29,4 +31,3 @@ sudo debconf-set-selections < /tmp/chef_seed
|
|||
rm -rf /tmp/chef_seed
|
||||
|
||||
sudo apt-get -y install chef chef-server chef-server-api chef-expander
|
||||
|
||||
|
|
|
@ -17,4 +17,3 @@ cd ~/chef-repo
|
|||
knife cookbook upload -a
|
||||
knife environment from file environments/*.json
|
||||
knife role from file roles/*.json
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
TODOs
|
||||
|
||||
- Networks:
|
||||
|
||||
eth0, management: inherent interface on each rVM
|
||||
|
@ -7,23 +9,6 @@ eth1, ops: 10.251.x.x/16
|
|||
eth2, private: 10.252.x.x/16
|
||||
eth3, public: 172.31.x.x/16
|
||||
|
||||
- Steps:
|
||||
|
||||
start 3 + 2 (default) or more VMs, via calling OpenStack Nova API
|
||||
|
||||
install chefserver, config knife, upload cookbooks, roles, and
|
||||
environments
|
||||
|
||||
check-in all other VMs into chefserver (knife bootstrap), via eth0
|
||||
|
||||
deploy VXLAN network via openvswitch cookbook for all VMs, i.e., build VXLAN
|
||||
tunnels with gateway as layer-2 hub and other VMs as spokes
|
||||
|
||||
deploy OpenStack controller(s) via misc cookbooks
|
||||
|
||||
deploy workers via misc cookbooks (parallelization via Python
|
||||
multi-threading or multi-processing)
|
||||
|
||||
- Others:
|
||||
|
||||
rVMs eth1 IPs
|
||||
|
@ -46,64 +31,221 @@ rest (sensitive data) in a private configuration file specific to each
|
|||
developer/user
|
||||
"""
|
||||
|
||||
import getopt
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from collections import OrderedDict
|
||||
|
||||
from novaclient.v1_1 import client
|
||||
import IPython
|
||||
from novaclient.v1_1.client import Client
|
||||
|
||||
from inception.utils import cmd
|
||||
|
||||
|
||||
class Orchestrator(object):
|
||||
"""
|
||||
orchestrate all inception cloud stuff
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
prefix,
|
||||
num_workers,
|
||||
user='ubuntu',
|
||||
image='3ab46178-eaae-46f0-8c13-6aad4d62ecde',
|
||||
flavor=3,
|
||||
key_name='shared',
|
||||
security_groups=('default', 'ssh'),
|
||||
src_dir='../bin/',
|
||||
dst_dir='/home/ubuntu/',
|
||||
chefserver_filenames=('install_chefserver.sh',
|
||||
'configure_knife.sh',
|
||||
'setup_chef_repo.sh'),
|
||||
timeout=60):
|
||||
"""
|
||||
@param prefix: unique name as prefix
|
||||
@param num_workers: how many worker nodes you'd like
|
||||
@param image: default u1204-130508-gv
|
||||
@param flavor: default medium
|
||||
@param key_name: ssh public key to be injected
|
||||
@param security_groups:
|
||||
@param src_dir: location from where scripts are uploaded to servers
|
||||
@param dst_dir: target location of scripts on servers
|
||||
@param chefserver_filenames: scripts to run on chefserver
|
||||
@param timeout: sleep time (s) for servers to be launched
|
||||
"""
|
||||
## args
|
||||
self.prefix = prefix
|
||||
self.num_workers = num_workers
|
||||
self.user = user
|
||||
self.image = image
|
||||
self.flavor = flavor
|
||||
self.key_name = key_name
|
||||
self.security_groups = security_groups
|
||||
self.src_dir = src_dir
|
||||
self.dst_dir = dst_dir
|
||||
self.chefserver_files = OrderedDict()
|
||||
for filename in chefserver_filenames:
|
||||
fin = open(os.path.join(self.src_dir, filename), 'r')
|
||||
value = fin.read()
|
||||
key = os.path.join(self.dst_dir, filename)
|
||||
self.chefserver_files[key] = value
|
||||
fin.close()
|
||||
self.timeout = timeout
|
||||
## entities
|
||||
self.client = Client(os.environ['OS_USERNAME'],
|
||||
os.environ['OS_PASSWORD'],
|
||||
os.environ['OS_TENANT_NAME'],
|
||||
os.environ['OS_AUTH_URL'])
|
||||
self.chefserver = None
|
||||
self.gateway = None
|
||||
self.controller = None
|
||||
self.workers = []
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
run the whole process
|
||||
"""
|
||||
try:
|
||||
self._create_servers()
|
||||
self._setup_chefserver()
|
||||
self._checkin_chefserver()
|
||||
self._deploy_vxlan_network()
|
||||
self._deploy_controller()
|
||||
self._deploy_workers()
|
||||
print "Your inception cloud is ready!!!"
|
||||
except Exception:
|
||||
print traceback.format_exc()
|
||||
self._cleanup()
|
||||
|
||||
def _create_servers(self):
|
||||
"""
|
||||
start all VM servers: chefserver, gateway, controller, and workers, via
|
||||
calling Nova client API
|
||||
"""
|
||||
# launch chefserver
|
||||
self.chefserver = self.client.servers.create(
|
||||
name=self.prefix + '-chefserver',
|
||||
image=self.image,
|
||||
flavor=self.flavor,
|
||||
key_name=self.key_name,
|
||||
security_groups=self.security_groups,
|
||||
files=self.chefserver_files)
|
||||
print "%s is being created" % self.chefserver.name
|
||||
|
||||
# launch gateway
|
||||
self.gateway = self.client.servers.create(
|
||||
name=self.prefix + '-gateway',
|
||||
image=self.image,
|
||||
flavor=self.flavor,
|
||||
key_name=self.key_name,
|
||||
security_groups=self.security_groups)
|
||||
print "%s is being created" % self.gateway.name
|
||||
|
||||
# launch controller
|
||||
self.controller = self.client.servers.create(
|
||||
name=self.prefix + '-controller',
|
||||
image=self.image,
|
||||
flavor=self.flavor,
|
||||
key_name=self.key_name,
|
||||
security_groups=self.security_groups)
|
||||
print "%s is being created" % self.controller.name
|
||||
|
||||
# launch workers
|
||||
self.workers = []
|
||||
for i in xrange(self.num_workers):
|
||||
worker = self.client.servers.create(
|
||||
name=self.prefix + '-worker%s' % (i + 1),
|
||||
image=self.image,
|
||||
flavor=self.flavor,
|
||||
key_name=self.key_name,
|
||||
security_groups=self.security_groups)
|
||||
self.workers.append(worker)
|
||||
print 'name %s is being created' % worker.name
|
||||
|
||||
print 'sleep %s seconds to wait for servers to be ready' % self.timeout
|
||||
time.sleep(self.timeout)
|
||||
|
||||
def _setup_chefserver(self):
|
||||
"""
|
||||
execute uploaded scripts to install chef, config knife, upload
|
||||
cookbooks, roles, and environments
|
||||
"""
|
||||
self.chefserver = self.client.servers.get(self.chefserver.id)
|
||||
if self.chefserver.status != 'ACTIVE':
|
||||
raise RuntimeError('%s can not be launched' % self.chefserver.name)
|
||||
# get ipaddress (there is only 1 item in the dict)
|
||||
for network in self.chefserver.networks:
|
||||
ipaddress = self.chefserver.networks[network][0]
|
||||
# execute scripts via ssh command
|
||||
out, error = cmd.ssh(self.user + '@' + ipaddress,
|
||||
'sudo /bin/umount /mnt')
|
||||
print 'out=', out, 'error=', error
|
||||
for key in self.chefserver_files:
|
||||
out, error = cmd.ssh(self.user + '@' + ipaddress,
|
||||
'/bin/bash ' + key,
|
||||
output_to_screen=True)
|
||||
print 'out=', out, 'error=', error
|
||||
|
||||
def _checkin_chefserver(self):
|
||||
"""
|
||||
check-in all other VMs into chefserver (knife bootstrap), via eth0
|
||||
"""
|
||||
|
||||
def _deploy_vxlan_network(self):
|
||||
"""
|
||||
deploy VXLAN network via openvswitch cookbook for all VMs, i.e., build
|
||||
VXLAN tunnels with gateway as layer-2 hub and other VMs as spokes
|
||||
"""
|
||||
|
||||
def _deploy_controller(self):
|
||||
"""
|
||||
deploy OpenStack controller(s) via misc cookbooks
|
||||
"""
|
||||
|
||||
def _deploy_workers(self):
|
||||
"""
|
||||
deploy workers via misc cookbooks (parallelization via Python
|
||||
multi-threading or multi-processing)
|
||||
"""
|
||||
|
||||
def _cleanup(self):
|
||||
"""
|
||||
blow up the whole inception cloud
|
||||
"""
|
||||
for server in self.client.servers:
|
||||
server.delete()
|
||||
print '%s is being deleted' % server.name
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
program starting point
|
||||
"""
|
||||
try:
|
||||
nova_client = client.Client(os.environ['OS_USERNAME'],
|
||||
os.environ['OS_PASSWORD'],
|
||||
os.environ['OS_TENANT_NAME'],
|
||||
os.environ['OS_AUTH_URL'])
|
||||
optlist, _ = getopt.getopt(sys.argv[1:], 'p:n:', [])
|
||||
optdict = dict(optlist)
|
||||
prefix = optdict['-p']
|
||||
if '-' in prefix:
|
||||
raise RuntimeError('"-" can not exist in prefix')
|
||||
num_workers = int(optdict['-n'])
|
||||
except Exception:
|
||||
print traceback.format_exc()
|
||||
usage()
|
||||
sys.exit(1)
|
||||
prefix = 'cliu-test'
|
||||
image = '3ab46178-eaae-46f0-8c13-6aad4d62ecde' # u1204-130508-gv
|
||||
flavor = 3 # medium
|
||||
key_name = 'shared'
|
||||
security_groups = ("chef", "ssh", "default")
|
||||
num_instances = 3
|
||||
servers = []
|
||||
for i in xrange(num_instances):
|
||||
name = '%s-%s' % (prefix, i)
|
||||
server = nova_client.servers.create(name=name,
|
||||
image=image,
|
||||
flavor=flavor,
|
||||
key_name=key_name,
|
||||
security_groups=security_groups,
|
||||
)
|
||||
servers.append(server)
|
||||
print 'server %s is being created' % name
|
||||
time.sleep(20)
|
||||
for server in servers:
|
||||
# server.reboot()
|
||||
server.delete()
|
||||
print 'server %s is being deleted' % server.name
|
||||
# for flavor in nova_client.flavors.list():
|
||||
# print repr(flavor)
|
||||
# for image in nova_client.images.list():
|
||||
# print repr(image)
|
||||
# for keypair in nova_client.keypairs.list():
|
||||
# print repr(keypair)
|
||||
# for security_group in nova_client.security_groups.list():
|
||||
# print repr(security_group)
|
||||
# print nova_client.images.get('3ab46178-eaae-46f0-8c13-6aad4d62ecde')
|
||||
# print nova_client.flavors.get(3)
|
||||
orchestrator = Orchestrator(prefix, num_workers)
|
||||
orchestrator.start()
|
||||
# give me a ipython shell when everything is done
|
||||
IPython.embed()
|
||||
|
||||
|
||||
def usage():
|
||||
print """
|
||||
First: make sure OpenStack-related environment variables are defined
|
||||
(make sure OpenStack-related environment variables are defined)
|
||||
|
||||
Then: python %s
|
||||
""" % (__file__)
|
||||
python %s -p <prefix> -n <num_workers>
|
||||
""" % (__file__,)
|
||||
|
||||
##############################################
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
"""Command execution utils
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def local(cmd):
|
||||
"""
|
||||
Execute a local command
|
||||
|
||||
@param cmd: a str, e.g., 'uname -a'
|
||||
"""
|
||||
print 'executing command=', cmd
|
||||
proc = subprocess.Popen(cmd,
|
||||
shell=True,
|
||||
stdin=None,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
out, error = proc.communicate() # 0: stdout, 1:stderr
|
||||
return out.rstrip('\n'), error # remove trailing '\n'
|
||||
|
||||
|
||||
def ssh(uri, cmd, silent=True, output_to_screen=False):
|
||||
"""
|
||||
Execute a remote command via ssh
|
||||
|
||||
@param uri: <user>@<ipaddr/hostname>[:port]
|
||||
@param cmd: a str, e.g., 'uname -a'
|
||||
@param silent: whether prompt for yes/no questions
|
||||
"""
|
||||
## if ssh port forwarding address, find out the port
|
||||
if ':' in uri:
|
||||
uri, port = uri.split(':')
|
||||
## default port
|
||||
else:
|
||||
port = 22
|
||||
flag = ('-T -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
|
||||
if silent else '-T')
|
||||
cmd = 'ssh -p %s %s %s %s' % (port, flag, uri, cmd)
|
||||
print 'executing command=', cmd
|
||||
if output_to_screen:
|
||||
out = subprocess.check_call([e for e in cmd.split(' ') if e])
|
||||
return (out, None)
|
||||
else:
|
||||
proc = subprocess.Popen(cmd,
|
||||
shell=True,
|
||||
stdin=None,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
out, error = proc.communicate() # 0: stdout, 1:stderr
|
||||
if any(s in error for s in ["No route to host",
|
||||
"Connection timed out",
|
||||
"Connection refused",
|
||||
"Connection closed by remote host"]):
|
||||
raise RuntimeError('host can not be reached via ssh')
|
||||
return out.rstrip('\n'), error # remove trailing '\n'
|
Loading…
Reference in New Issue