cmd 7.29 KB
Newer Older
Swann Perarnau's avatar
Swann Perarnau committed
1 2 3 4 5 6
#!/usr/bin/env python2

from __future__ import print_function
import argparse
import logging
import signal
7
import os
8
import nrm.messaging
9
import uuid
Swann Perarnau's avatar
Swann Perarnau committed
10

11 12
RPC_MSG = nrm.messaging.MSGTYPES['up_rpc_req']
logger = logging.getLogger('nrm')
13

Swann Perarnau's avatar
Swann Perarnau committed
14 15 16 17 18 19

class CommandLineInterface(object):

    """Implements a command line interface to the NRM."""

    def __init__(self):
20
        pass
Swann Perarnau's avatar
Swann Perarnau committed
21

22
    def do_signal(self, signum, stackframe):
23
        logger.info("received signal %d, exiting", signum)
24
        exit(1)
Swann Perarnau's avatar
Swann Perarnau committed
25 26

    def setup(self):
27 28 29 30
        # upstream RPC port
        upstream_client_port = 3456
        upstream_client_param = "tcp://localhost:%d" % (upstream_client_port)
        self.client = nrm.messaging.UpstreamRPCClient(upstream_client_param)
Swann Perarnau's avatar
Swann Perarnau committed
31 32 33 34

        # take care of signals
        signal.signal(signal.SIGINT, self.do_signal)

35
        self.client.wait_connected()
Swann Perarnau's avatar
Swann Perarnau committed
36 37

    def do_run(self, argv):
38 39 40
        """ Connect to the NRM and ask to spawn a container and run a command
        in it.

41
        The NRM should reply for container info."""
42 43 44 45

        # build the command as a JSON dict containing enough info. We add to
        # the command a container uuid as a way to make sure that we can make
        # the command idempotent.
46
        environ = os.environ
47 48
        command = {'api': 'up_rpc_req',
                   'type': 'run',
49
                   'manifest': argv.manifest,
50
                   'path': argv.command,
51
                   'args': argv.args,
52
                   'environ': dict(environ),
53
                   'container_uuid': argv.ucontainername or str(uuid.uuid4()),
54
                   }
55
        msg = RPC_MSG['run'](**command)
56 57 58 59 60
        # command fsm
        state = 'init'
        outeof = False
        erreof = False
        exitmsg = None
61
        self.client.sendmsg(msg)
62
        while(True):
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
            msg = self.client.recvmsg()
            assert msg.api == 'up_rpc_rep'
            assert msg.type in ['start', 'stdout', 'stderr', 'exit',
                                'process_start', 'process_exit']

            if msg.type == 'start':
                if state == 'init':
                    state = 'started'
                    logger.info("container started: %r", msg)
                else:
                    logger.info("unexpected start message: %r", state)
                    exit(1)
            elif msg.type == 'process_start':
                if state == 'init':
                    state = 'started'
                    logger.info("process started in existing "
                                "container: %r""", msg)
                else:
                    logger.info("unexpected start message: %r", state)
                    exit(1)
            elif msg.type == 'stdout':
                logger.info("container msg: %r", msg)
                if msg.payload == 'eof':
                    outeof = True
            elif msg.type == 'stderr':
                logger.info("container msg: %r", msg)
                if msg.payload == 'eof':
                    erreof = True
            elif msg.type == 'process_exit':
                logger.info("process ended: %r", msg)
                break
            elif msg.type == 'exit':
                if state == 'started':
                    state = 'exiting'
                    exitmsg = msg
                else:
                    logger.info("unexpected exit message: %r", msg)
100 101 102 103
            if outeof and erreof and state == 'exiting':
                state = 'exit'
                logger.info("container ended: %r", exitmsg)
                break
Swann Perarnau's avatar
Swann Perarnau committed
104

105 106 107 108
    def do_list(self, argv):
        """Connect to the NRM and ask to list the containers present on the
        system.

109 110
        The NRM should respond to us with one message listing all
        containers."""
111

112 113 114 115 116 117 118 119
        command = {'api': 'up_rpc_req',
                   'type': 'list'}
        msg = RPC_MSG['list'](**command)
        self.client.sendmsg(msg)
        msg = self.client.recvmsg()
        assert msg.api == 'up_rpc_rep'
        assert msg.type == 'list'
        logger.info("list response: %r", msg)
120

Swann Perarnau's avatar
Swann Perarnau committed
121 122 123
    def do_kill(self, argv):
        """Connect to the NRM and ask to kill a container by uuid.

124 125
        The NRM should respond to us with a message containing the exit status
        of the top process of the container."""
Swann Perarnau's avatar
Swann Perarnau committed
126

127 128 129
        command = {'api': 'up_rpc_req',
                   'type': 'kill',
                   'container_uuid': argv.uuid
Swann Perarnau's avatar
Swann Perarnau committed
130
                   }
131 132 133 134 135 136
        msg = RPC_MSG['kill'](**command)
        self.client.sendmsg(msg)
        msg = self.client.recvmsg()
        assert msg.api == 'up_rpc_rep'
        assert msg.type == 'exit'
        logger.info("container exit: %r", msg)
Swann Perarnau's avatar
Swann Perarnau committed
137

Swann Perarnau's avatar
Swann Perarnau committed
138 139 140
    def do_setpower(self, argv):
        """ Connect to the NRM and ask to change the power limit.

141
        The NRM should answer with an acknowledgment."""
Swann Perarnau's avatar
Swann Perarnau committed
142

143 144 145 146 147
        # build the command as a JSON dict giving enough info. This is an
        # idempotent command, so we will repeat the command if we don't get a
        # timely answer.
        # TODO: check that the level makes a little bit of sense in the first
        # place
148 149 150
        command = {'api': 'up_rpc_req',
                   'type': 'setpower',
                   'limit': str(argv.limit),
151
                   }
152 153 154 155 156 157
        msg = RPC_MSG['setpower'](**command)
        self.client.sendmsg(msg)
        msg = self.client.recvmsg()
        assert msg.api == 'up_rpc_rep'
        assert msg.type == 'getpower'
        logger.info("command received by the daemon: %r", msg)
Swann Perarnau's avatar
Swann Perarnau committed
158 159 160 161 162 163 164 165 166 167

    def main(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("-v", "--verbose",
                            help="verbose logging information",
                            action='store_true')
        subparsers = parser.add_subparsers()

        # run container
        parser_run = subparsers.add_parser("run")
168 169 170 171
        parser_run.add_argument("manifest", help="manifest file to apply")
        parser_run.add_argument("command", help="command to execute")
        parser_run.add_argument("args", help="command arguments",
                                nargs=argparse.REMAINDER)
172 173 174
        parser_run.add_argument("-u", "--ucontainername", help="""user-specified
                                name for container used to attach proceses""",
                                nargs='?', const=None, default=None)
Swann Perarnau's avatar
Swann Perarnau committed
175 176
        parser_run.set_defaults(func=self.do_run)

Swann Perarnau's avatar
Swann Perarnau committed
177 178 179 180 181
        # kill container
        parser_kill = subparsers.add_parser("kill")
        parser_kill.add_argument("uuid", help="uuid of the container")
        parser_kill.set_defaults(func=self.do_kill)

182 183 184 185
        # list containers
        parser_list = subparsers.add_parser("list")
        parser_list.set_defaults(func=self.do_list)

Swann Perarnau's avatar
Swann Perarnau committed
186 187 188 189 190
        # setpowerlimit
        parser_setpower = subparsers.add_parser("setpower")
        parser_setpower.add_argument("-f", "--follow",
                                     help="listen for power changes",
                                     action='store_true')
191
        parser_setpower.add_argument("limit",
Swann Perarnau's avatar
Swann Perarnau committed
192 193 194
                                     help="set new power limit",
                                     type=float)
        parser_setpower.set_defaults(func=self.do_setpower)
195

Swann Perarnau's avatar
Swann Perarnau committed
196 197
        args = parser.parse_args()
        if args.verbose:
198
            logger.setLevel(logging.DEBUG)
Swann Perarnau's avatar
Swann Perarnau committed
199 200 201 202 203 204 205 206 207

        self.setup()
        args.func(args)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    cli = CommandLineInterface()
    cli.main()