cmd 7.26 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
Swann Perarnau's avatar
Swann Perarnau committed
9

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

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

class CommandLineInterface(object):

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

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

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

    def setup(self):
26 27 28 29
        # 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
30 31 32 33

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

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

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

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

        # 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.
45
        environ = os.environ
46 47
        command = {'api': 'up_rpc_req',
                   'type': 'run',
48
                   'manifest': argv.manifest,
49
                   'path': argv.command,
50
                   'args': argv.args,
51
                   'environ': dict(environ),
52
                   'container_uuid': str(argv.ucontainername),
53
                   }
54
        msg = RPC_MSG['run'](**command)
55 56 57 58 59
        # command fsm
        state = 'init'
        outeof = False
        erreof = False
        exitmsg = None
60
        self.client.sendmsg(msg)
61
        while(True):
62 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
            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)
99 100 101 102
            if outeof and erreof and state == 'exiting':
                state = 'exit'
                logger.info("container ended: %r", exitmsg)
                break
Swann Perarnau's avatar
Swann Perarnau committed
103

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

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

111 112 113 114 115 116 117 118
        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)
119

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

123 124
        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
125

126 127 128
        command = {'api': 'up_rpc_req',
                   'type': 'kill',
                   'container_uuid': argv.uuid
Swann Perarnau's avatar
Swann Perarnau committed
129
                   }
130 131 132 133 134 135
        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
136

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

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

142 143 144 145 146
        # 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
147 148 149
        command = {'api': 'up_rpc_req',
                   'type': 'setpower',
                   'limit': str(argv.limit),
150
                   }
151 152 153 154 155 156
        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
157 158 159 160 161 162 163 164 165 166

    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")
167 168 169 170
        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)
171 172 173
        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
174 175
        parser_run.set_defaults(func=self.do_run)

Swann Perarnau's avatar
Swann Perarnau committed
176 177 178 179 180
        # 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)

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

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

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

        self.setup()
        args.func(args)


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