~lutostag/ubuntu/trusty/maas/1.5.4+keystone

« back to all changes in this revision

Viewing changes to src/metadataserver/commissioning/snippets/maas_signal.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2013-03-04 11:49:44 UTC
  • mto: This revision was merged to the branch mainline in revision 25.
  • Revision ID: package-import@ubuntu.com-20130304114944-azcvu9anlf8mizpa
Tags: upstream-1.3+bzr1452+dfsg
ImportĀ upstreamĀ versionĀ 1.3+bzr1452+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
import json
 
4
import mimetypes
 
5
import os.path
 
6
import random
 
7
import socket
 
8
import string
 
9
import sys
 
10
import urllib2
 
11
 
 
12
from maas_api_helper import (
 
13
    geturl,
 
14
    read_config,
 
15
    )
 
16
 
 
17
 
 
18
MD_VERSION = "2012-03-01"
 
19
VALID_STATUS = ("OK", "FAILED", "WORKING")
 
20
POWER_TYPES = ("ipmi", "virsh", "ether_wake")
 
21
 
 
22
 
 
23
def _encode_field(field_name, data, boundary):
 
24
    return ('--' + boundary,
 
25
            'Content-Disposition: form-data; name="%s"' % field_name,
 
26
            '', str(data))
 
27
 
 
28
 
 
29
def _encode_file(name, fileObj, boundary):
 
30
    return ('--' + boundary,
 
31
            'Content-Disposition: form-data; name="%s"; filename="%s"' %
 
32
                (name, name),
 
33
            'Content-Type: %s' % _get_content_type(name),
 
34
            '', fileObj.read())
 
35
 
 
36
 
 
37
def _random_string(length):
 
38
    return ''.join(random.choice(string.letters) for ii in range(length + 1))
 
39
 
 
40
 
 
41
def _get_content_type(filename):
 
42
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
 
43
 
 
44
 
 
45
def encode_multipart_data(data, files):
 
46
    """Create a MIME multipart payload from L{data} and L{files}.
 
47
 
 
48
    @param data: A mapping of names (ASCII strings) to data (byte string).
 
49
    @param files: A mapping of names (ASCII strings) to file objects ready to
 
50
        be read.
 
51
    @return: A 2-tuple of C{(body, headers)}, where C{body} is a a byte string
 
52
        and C{headers} is a dict of headers to add to the enclosing request in
 
53
        which this payload will travel.
 
54
    """
 
55
    boundary = _random_string(30)
 
56
 
 
57
    lines = []
 
58
    for name in data:
 
59
        lines.extend(_encode_field(name, data[name], boundary))
 
60
    for name in files:
 
61
        lines.extend(_encode_file(name, files[name], boundary))
 
62
    lines.extend(('--%s--' % boundary, ''))
 
63
    body = '\r\n'.join(lines)
 
64
 
 
65
    headers = {'content-type': 'multipart/form-data; boundary=' + boundary,
 
66
               'content-length': str(len(body))}
 
67
 
 
68
    return body, headers
 
69
 
 
70
 
 
71
def fail(msg):
 
72
    sys.stderr.write("FAIL: %s" % msg)
 
73
    sys.exit(1)
 
74
 
 
75
 
 
76
def main():
 
77
    """
 
78
    Call with single argument of directory or http or https url.
 
79
    If url is given additional arguments are allowed, which will be
 
80
    interpreted as consumer_key, token_key, token_secret, consumer_secret.
 
81
    """
 
82
    import argparse
 
83
 
 
84
    parser = argparse.ArgumentParser(
 
85
        description='Send signal operation and optionally post files to MAAS')
 
86
    parser.add_argument("--config", metavar="file",
 
87
        help="Specify config file", default=None)
 
88
    parser.add_argument("--ckey", metavar="key",
 
89
        help="The consumer key to auth with", default=None)
 
90
    parser.add_argument("--tkey", metavar="key",
 
91
        help="The token key to auth with", default=None)
 
92
    parser.add_argument("--csec", metavar="secret",
 
93
        help="The consumer secret (likely '')", default="")
 
94
    parser.add_argument("--tsec", metavar="secret",
 
95
        help="The token secret to auth with", default=None)
 
96
    parser.add_argument("--apiver", metavar="version",
 
97
        help="The apiver to use ("" can be used)", default=MD_VERSION)
 
98
    parser.add_argument("--url", metavar="url",
 
99
        help="The data source to query", default=None)
 
100
    parser.add_argument("--file", dest='files',
 
101
        help="File to post", action='append', default=[])
 
102
    parser.add_argument("--post", dest='posts',
 
103
        help="name=value pairs to post", action='append', default=[])
 
104
    parser.add_argument("--power-type", dest='power_type',
 
105
        help="Power type.", choices=POWER_TYPES, default=None)
 
106
    parser.add_argument("--power-parameters", dest='power_parms',
 
107
        help="Power parameters.", default=None)
 
108
    parser.add_argument(
 
109
        "--script-result", metavar="retval", type=int, dest='script_result',
 
110
        help="Return code of a commissioning script.")
 
111
 
 
112
    parser.add_argument("status",
 
113
        help="Status", choices=VALID_STATUS, action='store')
 
114
    parser.add_argument("message", help="Optional message",
 
115
        default="", nargs='?')
 
116
 
 
117
    args = parser.parse_args()
 
118
 
 
119
    creds = {
 
120
        'consumer_key': args.ckey,
 
121
        'token_key': args.tkey,
 
122
        'token_secret': args.tsec,
 
123
        'consumer_secret': args.csec,
 
124
        'metadata_url': args.url,
 
125
        }
 
126
 
 
127
    if args.config:
 
128
        read_config(args.config, creds)
 
129
 
 
130
    url = creds.get('metadata_url', None)
 
131
    if not url:
 
132
        fail("URL must be provided either in --url or in config\n")
 
133
    url = "%s/%s/" % (url, args.apiver)
 
134
 
 
135
    params = {
 
136
        "op": "signal",
 
137
        "status": args.status,
 
138
        "error": args.message,
 
139
        }
 
140
 
 
141
    if args.script_result is not None:
 
142
        params['script_result'] = args.script_result
 
143
 
 
144
    for ent in args.posts:
 
145
        try:
 
146
            (key, val) = ent.split("=", 2)
 
147
        except ValueError:
 
148
            sys.stderr.write("'%s' had no '='" % ent)
 
149
            sys.exit(1)
 
150
        params[key] = val
 
151
 
 
152
    if args.power_parms is not None:
 
153
        params["power_type"] = args.power_type
 
154
        power_parms = dict(
 
155
            power_user=args.power_parms.split(",")[0],
 
156
            power_pass=args.power_parms.split(",")[1],
 
157
            power_address=args.power_parms.split(",")[2]
 
158
            )
 
159
        params["power_parameters"] = json.dumps(power_parms)
 
160
 
 
161
    files = {}
 
162
    for fpath in args.files:
 
163
        files[os.path.basename(fpath)] = open(fpath, "r")
 
164
 
 
165
    data, headers = encode_multipart_data(params, files)
 
166
 
 
167
    exc = None
 
168
    msg = ""
 
169
 
 
170
    try:
 
171
        payload = geturl(url, creds=creds, headers=headers, data=data)
 
172
        if payload != "OK":
 
173
            raise TypeError("Unexpected result from call: %s" % payload)
 
174
        else:
 
175
            msg = "Success"
 
176
    except urllib2.HTTPError as exc:
 
177
        msg = "http error [%s]" % exc.code
 
178
    except urllib2.URLError as exc:
 
179
        msg = "url error [%s]" % exc.reason
 
180
    except socket.timeout as exc:
 
181
        msg = "socket timeout [%s]" % exc
 
182
    except TypeError as exc:
 
183
        msg = exc.message
 
184
    except Exception as exc:
 
185
        msg = "unexpected error [%s]" % exc
 
186
 
 
187
    sys.stderr.write("%s\n" % msg)
 
188
    sys.exit((exc is None))
 
189
 
 
190
if __name__ == '__main__':
 
191
    main()