Merge lp:~doanac/lava-lab/lava-scripts into lp:lava-lab

Proposed by Andy Doan
Status: Needs review
Proposed branch: lp:~doanac/lava-lab/lava-scripts
Merge into: lp:lava-lab
Diff against target: 380 lines (+341/-0)
7 files modified
_grains/lava.py (+6/-0)
scripts/lava-add-worker (+22/-0)
scripts/lava-start (+20/-0)
scripts/lava-status (+19/-0)
scripts/lava-stop (+22/-0)
scripts/lava-upgrade (+34/-0)
scripts/lava_salt.py (+218/-0)
To merge this branch: bzr merge lp:~doanac/lava-lab/lava-scripts
Reviewer Review Type Date Requested Status
Linaro Validation Team Pending
Review via email: mp+146547@code.launchpad.net

Description of the change

start work on some helper functions that should allow us to manage lava deployments using salt

To post a comment you must log in.
Revision history for this message
Antonio Terceiro (terceiro) wrote :

This looks awesome!

One general comment: you don't need to run l-d-t as root. I did the following
on the vagrant-bootstrap script inside the l-d-t repo:

if ! test -d /srv/lava/instances/development; then
  cd $basedir
  su vagrant -c './lava-deployment-tool setup -nd'
  su vagrant -c './lava-deployment-tool install -nd development'
fi

so you could do something like that, and you won't need to chown/chmod away, or
even add that check on l-d-t to allow running as root.

--
Antonio Terceiro
Software Engineer - Linaro
http://www.linaro.org

Revision history for this message
Andy Doan (doanac) wrote :

On 02/05, Antonio Terceiro wrote:
> One general comment: you don't need to run l-d-t as root. I did the following
> on the vagrant-bootstrap script inside the l-d-t repo:
>
> if ! test -d /srv/lava/instances/development; then
> cd $basedir
> su vagrant -c './lava-deployment-tool setup -nd'
> su vagrant -c './lava-deployment-tool install -nd development'
> fi

You must be setting up your "vagrant" user to allow password-less sudo?
We don't do that for our instance-manager and it seems a little scare
(to me) to allow that.

> so you could do something like that, and you won't need to chown/chmod away, or
> even add that check on l-d-t to allow running as root.

Revision history for this message
Antonio Terceiro (terceiro) wrote :

On Tue, Feb 05, 2013 at 06:46:22PM -0000, Andy Doan wrote:
> On 02/05, Antonio Terceiro wrote:
> > One general comment: you don't need to run l-d-t as root. I did the following
> > on the vagrant-bootstrap script inside the l-d-t repo:
> >
> > if ! test -d /srv/lava/instances/development; then
> > cd $basedir
> > su vagrant -c './lava-deployment-tool setup -nd'
> > su vagrant -c './lava-deployment-tool install -nd development'
> > fi
>
> You must be setting up your "vagrant" user to allow password-less sudo?
> We don't do that for our instance-manager and it seems a little scare
> (to me) to allow that.

in that case we could setup password-less sudo for instance-manager
exclusively for running the lava-deployment-tool command.

--
Antonio Terceiro
Software Engineer - Linaro
http://www.linaro.org

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Andy Doan <email address hidden> writes:

> Andy Doan has proposed merging lp:~doanac/lava-lab/lava-scripts into lp:lava-lab.
>
> Requested reviews:
> Linaro Validation Team (linaro-validation)
>
> For more details, see:
> https://code.launchpad.net/~doanac/lava-lab/lava-scripts/+merge/146547
>
> start work on some helper functions that should allow us to manage lava deployments using salt
> --
> https://code.launchpad.net/~doanac/lava-lab/lava-scripts/+merge/146547
> You are subscribed to branch lp:lava-lab.
> === modified file '_grains/lava.py'
> --- _grains/lava.py 2013-01-10 16:37:55 +0000
> +++ _grains/lava.py 2013-02-05 04:19:20 +0000
> @@ -9,4 +9,10 @@
> p = os.path.join(instdir, f)
> if os.path.isdir(p):
> insts.append(p)
> + # This seems to be a bug in salt. Even after you reload/resync grains
> + # the module still uses what it read at the time the minion started up.
> + # this makes this dynamic so we don't have to restart salt just to see
> + # a new instance
> + if 'lava_instances' in __grains__:
> + __grains__['lava_instances'] = insts

Thats because grains are supposed to be invariant. From
http://docs.saltstack.org/en/latest/topics/targeting/grains.html:

    It is important to remember that grains are bits of information
    loaded when the salt minion starts, so this information is
    static. This means that the information in grains is unchanging,
    therefore the nature of the data is static. So grains information
    are things like the running kernel, or the operating system.

I think grains-but-varying-things are "pillars", but not completely
sure. Might be worth asking #saltstack IRC...

Revision history for this message
Andy Doan (doanac) wrote :

On 02/05, Antonio Terceiro wrote:
> in that case we could setup password-less sudo for instance-manager
> exclusively for running the lava-deployment-tool command.

l-d-t runs too many sudo commands to make a sane sudoers entry. Its
really an all or nothing approach. So I'd say we just allow this hack in
l-d-t for now.

Unmerged revisions

69. By Andy Doan

minor bug from last commit

make sure we return the most interest part of this function

68. By Andy Doan

add support for installing a remote worker instance

this makes adding a new worker node as simple as possible

67. By Andy Doan

add some lava scripts to help manage lab

lava-status - shows the nodes contributing to a lava deployment, if
they are running, and which is the master instance

lava-{start/stop} - start/stop each lava-instance contributing to a lava deployment

lava-upgrade - performs an upgrade for a lava-deployment

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '_grains/lava.py'
--- _grains/lava.py 2013-01-10 16:37:55 +0000
+++ _grains/lava.py 2013-02-05 04:19:20 +0000
@@ -9,4 +9,10 @@
9 p = os.path.join(instdir, f)9 p = os.path.join(instdir, f)
10 if os.path.isdir(p):10 if os.path.isdir(p):
11 insts.append(p)11 insts.append(p)
12 # This seems to be a bug in salt. Even after you reload/resync grains
13 # the module still uses what it read at the time the minion started up.
14 # this makes this dynamic so we don't have to restart salt just to see
15 # a new instance
16 if 'lava_instances' in __grains__:
17 __grains__['lava_instances'] = insts
12 return {'lava_instances': insts}18 return {'lava_instances': insts}
1319
=== added directory 'scripts'
=== added file 'scripts/lava-add-worker'
--- scripts/lava-add-worker 1970-01-01 00:00:00 +0000
+++ scripts/lava-add-worker 2013-02-05 04:19:20 +0000
@@ -0,0 +1,22 @@
1#!/usr/bin/env python
2
3import argparse
4
5import lava_salt
6
7
8if __name__ == '__main__':
9 parser = argparse.ArgumentParser(description=lava_salt.add_worker.__doc__)
10 parser.add_argument('minion', metavar='<minion>',
11 help='The host to install the lava worker instance on.')
12 parser.add_argument('ip', metavar='<ip>',
13 help='The public IP address for the minion.')
14 parser.add_argument('instance', metavar='<instance>',
15 help='The instance name we are creating on the worker')
16 parser.add_argument('--dry-run', dest='dryrun', action='store_true',
17 help='Just display what would be changed')
18 args = parser.parse_args()
19
20 client = lava_salt.salt_client()
21 ret = lava_salt.add_worker(client, args.minion, args.ip, args.instance, args.dryrun)
22 print ret[args.minion]
023
=== added file 'scripts/lava-start'
--- scripts/lava-start 1970-01-01 00:00:00 +0000
+++ scripts/lava-start 2013-02-05 04:19:20 +0000
@@ -0,0 +1,20 @@
1#!/usr/bin/env python
2
3import argparse
4
5import lava_salt
6
7
8if __name__ == '__main__':
9 parser = argparse.ArgumentParser(description=lava_salt.start.__doc__)
10 parser.add_argument('instance', metavar='<instance>',
11 help='The instance name to start')
12 args = parser.parse_args()
13
14 client = lava_salt.salt_client()
15 ret = lava_salt.start(client, args.instance)
16 if ret:
17 print 'salt started the following instances:'
18 print ret
19 else:
20 print 'Instance already running on all minions'
021
=== added file 'scripts/lava-status'
--- scripts/lava-status 1970-01-01 00:00:00 +0000
+++ scripts/lava-status 2013-02-05 04:19:20 +0000
@@ -0,0 +1,19 @@
1#!/usr/bin/env python
2
3import argparse
4
5import lava_salt
6
7
8if __name__ == '__main__':
9 parser = argparse.ArgumentParser(description=lava_salt.info.__doc__)
10 parser.add_argument('instance', metavar='<instance>',
11 help='The instance name to stop')
12 args = parser.parse_args()
13
14 client = lava_salt.salt_client()
15
16 for host, props in lava_salt.info(client, args.instance).iteritems():
17 running = lava_salt.STATE_STRING[props['running']]
18 master = props['master']
19 print '{0}: running({1}) master({2})'.format(host, running, master)
020
=== added file 'scripts/lava-stop'
--- scripts/lava-stop 1970-01-01 00:00:00 +0000
+++ scripts/lava-stop 2013-02-05 04:19:20 +0000
@@ -0,0 +1,22 @@
1#!/usr/bin/env python
2
3import argparse
4
5import lava_salt
6
7
8if __name__ == '__main__':
9 parser = argparse.ArgumentParser(description=lava_salt.stop.__doc__)
10 parser.add_argument('--just-workers', dest='just_workers', action='store_true',
11 help='Just stop worker instances, not the master')
12 parser.add_argument('instance', metavar='<instance>',
13 help='The instance name to stop')
14 args = parser.parse_args()
15
16 client = lava_salt.salt_client()
17 ret = lava_salt.stop(client, args.instance, args.just_workers)
18 if ret:
19 print 'salt stopped the following instances:'
20 print ret
21 else:
22 print 'Instance not running on any minions'
023
=== added file 'scripts/lava-upgrade'
--- scripts/lava-upgrade 1970-01-01 00:00:00 +0000
+++ scripts/lava-upgrade 2013-02-05 04:19:20 +0000
@@ -0,0 +1,34 @@
1#!/usr/bin/env python
2
3import argparse
4
5import lava_salt
6
7
8def _indented(buff, indent_char):
9 indent_char = '\n' + indent_char
10 return ' ' + indent_char.join(buff.split('\n'))
11
12
13if __name__ == '__main__':
14 parser = argparse.ArgumentParser(description=lava_salt.upgrade.__doc__)
15 parser.add_argument('instance', metavar='<instance>',
16 help='The instance name to upgrade')
17 parser.add_argument('--dry-run', dest='dryrun', action='store_true',
18 help='Just display what would be changed')
19 args = parser.parse_args()
20
21 client = lava_salt.salt_client()
22 m_ret, w_ret = lava_salt.upgrade(client, args.instance, args.dryrun)
23
24 print 'Master:'
25 for host, msg in m_ret.iteritems():
26 print ' {0}:'.format(host)
27 print ' upgrade:\n{0}'.format(_indented(msg, ' |'))
28
29 print '\nWorkers:'
30 for host, rets in w_ret.iteritems():
31 print ' {0}:'.format(host)
32 print ' stop:\n{0}'.format(_indented(rets['stop'], ' |'))
33 print ' upgrade:\n{0}'.format(_indented(rets['upgrade'], ' |'))
34 print ' start:\n{0}'.format(_indented(rets['start'], ' |'))
035
=== added file 'scripts/lava_salt.py'
--- scripts/lava_salt.py 1970-01-01 00:00:00 +0000
+++ scripts/lava_salt.py 2013-02-05 04:19:20 +0000
@@ -0,0 +1,218 @@
1import salt.client
2
3RUNNING = 0
4STOPPED = 1
5UNKNOWN = 3
6
7STATE_STRING = {
8 RUNNING: 'running',
9 STOPPED: 'stopped',
10 UNKNOWN: '???',
11}
12
13LDT = '/home/instance-manager/lava-deployment-tool/lava-deployment-tool'
14
15
16def salt_client():
17 return salt.client.LocalClient()
18
19
20def info(client, instance):
21 """
22 Shows whether an instance of LAVA is running or not on its configured hosts.
23 """
24 cmd = 'status lava-instance LAVA_INSTANCE={0}'.format(instance)
25 inst_path = '/srv/lava/instances/{0}'.format(instance)
26 worker_file = '{0}/sbin/mount-masterfs'.format(inst_path)
27
28 inf = {}
29
30 ret = client.cmd('*', 'grains.item', ['lava_instances'])
31 for k, v in ret.iteritems():
32 if inst_path in v:
33 ret = client.cmd(k, 'cmd.run', [cmd])
34 running = UNKNOWN
35 if ret[k] == 'status: Unknown instance: %s' % instance:
36 running = STOPPED
37 elif ret[k] == 'lava-instance (%s) start/running' % instance:
38 running = RUNNING
39
40 ret = client.cmd(k, 'file.file_exists', [worker_file])
41 master = not ret[k]
42
43 inf[k] = {'running': running, 'master': master}
44 return inf
45
46
47def stop(client, instance, just_workers=False):
48 """
49 Issues a command to stop a given instance name on all minions where the
50 LAVA instance appears to be running.
51 """
52 cmd = 'stop lava-instance LAVA_INSTANCE={0}'.format(instance)
53
54 hosts = []
55 for host, props in info(client, instance).iteritems():
56 if props['running'] != STOPPED:
57 if not just_workers or not props['master']:
58 hosts.append(host)
59
60 if len(hosts):
61 return client.cmd(hosts, 'cmd.run', [cmd], expr_form='list')
62
63
64def start(client, instance):
65 """
66 Issues a command to start a given instance name on all minions where the
67 LAVA instance appears to not be running.
68 """
69 cmd = 'start lava-instance LAVA_INSTANCE={0}'.format(instance)
70
71 hosts = []
72 for host, props in info(client, instance).iteritems():
73 if props['running'] != RUNNING:
74 hosts.append(host)
75
76 if len(hosts):
77 return client.cmd(hosts, 'cmd.run', [cmd], expr_form='list')
78
79
80def upgrade(client, instance, dry_run=True):
81 """
82 Runs lava-deployment-tool upgrade for a LAVA setup. It first shuts down
83 each worker node instance. Then it runs lava-deployment-tool upgrade on
84 the master. Lastly, it runs lava-deployment-tool upgradeworker on the
85 worker nodes.
86 """
87 timeout = 300 # 5 minutes
88 workers = []
89 master = None
90
91 for host, props in info(client, instance).iteritems():
92 if props['master']:
93 assert not master, 'Detected multiple master instances in LAVA deployment'
94 master = host
95 else:
96 workers.append(host)
97
98 assert master, 'No master instance found in LAVA deployment'
99
100 w_ret = {}
101 for h in workers:
102 w_ret[h] = {'stop': 'dry-run', 'upgrade': 'dry-run', 'start': 'dry-run'}
103
104 if dry_run:
105 m_ret = {master: 'dry-run: upgrade master'}
106 return m_ret, w_ret
107
108 # first stop workers. This prevents a DB error if the upgrade changes
109 # the schema.
110 ret = stop(client, instance, True)
111 if ret:
112 for host, msg in ret.iteritems():
113 w_ret[host]['stop'] = msg
114
115 # now upgrade the master node
116 cmd = 'SKIP_ROOT_CHECK=yes {0} upgrade {1}'.format(LDT, instance)
117 m_ret = client.cmd(master, 'cmd.run', [cmd], timeout=timeout)
118
119 # now upgrade the workers
120 cmd = 'SKIP_ROOT_CHECK=yes {0} upgradeworker {1}'.format(LDT, instance)
121 if len(workers):
122 ret = client.cmd(workers, 'cmd.run', [cmd], timeout=timeout, expr_form='list')
123 for host, msg in ret.iteritems():
124 w_ret[host]['upgrade'] = msg
125
126 ret = start(client, instance)
127 if ret:
128 for host, msg in ret.iteritems():
129 if host in w_ret:
130 w_ret[host]['start'] = msg
131
132 # last thing: l-d-t ran as root, lets chmod things
133 cmd = 'chown -R instance-manager:instance-manager /srv/lava/instances/{0}/code/*'.format(instance)
134 client.cmd(workers + [master], 'cmd.run', [cmd], expr_form='list')
135
136 return m_ret, w_ret
137
138
139def _update_props(inifile_content, props):
140 for line in inifile_content.split('\n'):
141 if not line.strip().startswith('#'):
142 key, val = line.split('=')
143 if key in props:
144 props[key] = val.replace("'", '')
145
146
147def add_worker(client, minion, minion_ip, instance, dry_run=True):
148 """
149 Creates a new lava workernode on a salt-minion.
150 """
151
152 args = {
153 'LAVA_SERVER_IP': None,
154 'LAVA_SYS_USER': None,
155 'LAVA_PROXY': None,
156 'masterdir': '/srv/lava/instances/{0}'.format(instance),
157 'workerip': minion_ip,
158 'ldt': LDT,
159 'instance': instance,
160 'dbuser': None,
161 'dbpass': None,
162 'dbname': None,
163 'dbserver': None,
164 }
165
166 # ensure the instance exists and isn't already installed on the minion
167 master = None
168 for host, props in info(client, instance).iteritems():
169 if props['master']:
170 assert not master, 'Detected multiple master instances in LAVA deployment'
171 master = host
172 assert minion != host, 'LAVA instance already deployed on minion'
173
174 assert master, 'No master instance found in LAVA deployment'
175
176 # determine settings needed by looking at master instance
177 cmd = 'cat {0}/instance.conf'.format(args['masterdir'])
178 ret = client.cmd(master, 'cmd.run', [cmd])
179 _update_props(ret[master], args)
180
181 # get the db information
182 cmd = 'cat {0}/etc/lava-server/default_database.conf'.format(args['masterdir'])
183 ret = client.cmd(master, 'cmd.run', [cmd])
184 _update_props(ret[master], args)
185 if not args['dbserver']:
186 args['dbserver'] = args['LAVA_SERVER_IP']
187
188 cmd = ('SKIP_ROOT_CHECK=yes '
189 'LAVA_DB_SERVER={dbserver} LAVA_DB_NAME={dbname} '
190 'LAVA_DB_USER={dbuser} LAVA_DB_PASSWORD={dbpass} '
191 'LAVA_REMOTE_FS_HOST={LAVA_SERVER_IP} '
192 'LAVA_REMOTE_FS_USER={LAVA_SYS_USER} LAVA_REMOTE_FS_DIR={masterdir} '
193 'LAVA_PROXY="{LAVA_PROXY}" LAVA_SERVER_IP={workerip} '
194 '{ldt} installworker -n {instance} 2>&1 | tee /tmp/ldt.log'.format(**args))
195
196 if dry_run:
197 return {minion: 'dry-run: {0}'.format(cmd)}
198
199 ret = client.cmd(minion, 'cmd.run', [cmd], timeout=600)
200
201 # l-d-t ran as root, lets chmod things
202 cmd = 'chown -R instance-manager:instance-manager /srv/lava/instances/{0}/code/*'.format(instance)
203 client.cmd(minion, 'cmd.run', [cmd])
204
205 # now add the pubkey of the minion to the master's list of authorized keys
206 cmd = 'cat /srv/lava/instances/{0}/home/.ssh/id_rsa.pub'.format(instance)
207 pubkey = client.cmd(minion, 'cmd.run', [cmd])
208 pubkey = pubkey[minion].replace('ssh key used by LAVA for sshfs', minion)
209 authorized_keys = '{0}/home/.ssh/authorized_keys'.format(args['masterdir'])
210 client.cmd(master, 'file.append', [authorized_keys, pubkey])
211
212 # salt doens't seem to properly deal with grains that are dynamic.
213 # and calling this only once doesn't get the grain updated
214 # see _grains/lava.py for further details
215 client.cmd(minion, 'saltutil.sync_grains', [])
216 client.cmd(minion, 'saltutil.sync_grains', [])
217
218 return ret

Subscribers

People subscribed via source and target branches