def provision_self(update_manager): """ Provision the node. Returns a deferred. """ name = "node-{:x}".format(devices.get_hardware_serial()) conf = read_provisioning_conf() batch_id = conf.get("batch_id", None) batch_key = conf.get("batch_key", None) def cbresponse(response): router = response.data['router'] nexus.core.provision(router['_id']) nexus.core.saveKey(router['password'], 'apitoken') nexus.core.jwt_valid = True batch = response.data['batch'] hostconfig_patch = batch.get("hostconfig_patch", []) zerotier_networks = batch.get("zerotier_networks", []) update_manager.add_provision_update(hostconfig_patch, zerotier_networks) write_provisioning_result(response.data) data = { "name": name, "key": batch_key, "zerotier_address": zerotier.getAddress() } request = PDServerRequest('/api/batches/{}/provision'.format(batch_id)) d = request.post(**data) d.addCallback(cbresponse) return d
def send(self, report): request = PDServerRequest('/api/routers/{router_id}/' + self.model) d = request.post(**report) # Check for error code and retry. def cbresponse(response): if not response.success: out.warn('{} to {} returned code {}'.format( request.method, request.url, response.code)) if self.max_retries is None or self.retries < self.max_retries: reactor.callLater(self.retryDelay, self.send, report) self.retries += 1 self.increaseDelay() nexus.core.jwt_valid = False else: nexus.core.jwt_valid = True # Check for connection failures and retry. def cberror(ignored): out.warn('{} to {} failed'.format(request.method, request.url)) if self.max_retries is None or self.retries < self.max_retries: reactor.callLater(self.retryDelay, self.send, report) self.retries += 1 self.increaseDelay() nexus.core.jwt_valid = False d.addCallback(cbresponse) d.addErrback(cberror) return d
def pull_update(self, _auto=False): """ Start updates by polling the server for the latest updates. This is the only method that needs to be called from outside. The rest are triggered asynchronously. Call chain: pull_update -> _updates_received -> _update_complete _auto: Set to True when called by the scheduled LoopingCall. """ # If this call was triggered externally (_auto=False), then reset the # timer for the next scheduled call so we do not poll again too soon. if not _auto and self.scheduled_call.running: self.scheduled_call.reset() def handle_error(error): print("Request for updates failed: {}".format(error.getErrorMessage())) request = PDServerRequest('/api/routers/{router_id}/updates') d = request.get(completed=False) d.addCallback(self._updates_received) d.addErrback(handle_error) yield d
def _update_complete(self, update): """ Internal: callback after an update operation has been completed (successfuly or otherwise) and send a notification to the server. """ # If delegated to an external program, we do not report to pdserver # that the update is complete. if update.delegated: return out.info("The update is done, report it to server...") update_id = update.external['update_id'] success = update.result.get('success', False) request = PDServerRequest('/api/routers/{router_id}/updates/' + str(update_id)) d = request.patch( { 'op': 'replace', 'path': '/completed', 'value': True }, { 'op': 'replace', 'path': '/success', 'value': success }) return d
def provision(self, request): """ Provision the device with credentials from a cloud controller. """ cors.config_cors(request) body = json.loads(request.content.read().decode('utf-8')) routerId = body['routerId'] apitoken = body['apitoken'] pdserver = body['pdserver'] wampRouter = body['wampRouter'] changed = False if routerId != nexus.core.info.pdid \ or pdserver != nexus.core.info.pdserver \ or wampRouter != nexus.core.info.wampRouter: if pdserver and wampRouter: nexus.core.provision(routerId, pdserver, wampRouter) else: nexus.core.provision(routerId) changed = True if apitoken != nexus.core.getKey('apitoken'): nexus.core.saveKey(apitoken, 'apitoken') changed = True if changed: PDServerRequest.resetToken() nexus.core.jwt_valid = False def set_update_fetcher(session): session.set_update_fetcher(self.update_fetcher) @inlineCallbacks def start_polling(result): yield self.update_fetcher.start_polling() def send_response(result): response = dict() response['provisioned'] = True response['httpConnected'] = nexus.core.jwt_valid response['wampConnected'] = nexus.core.wamp_connected request.setHeader('Content-Type', 'application/json') return json.dumps(response) wampDeferred = nexus.core.connect(WampSession) wampDeferred.addCallback(set_update_fetcher) httpDeferred = sendStateReport() httpDeferred.addCallback(start_polling) identDeferred = sendNodeIdentity() dl = DeferredList([wampDeferred, httpDeferred, identDeferred], consumeErrors=True) dl.addBoth(send_response) reactor.callLater(6, dl.cancel) return dl else: return json.dumps({'success': False, 'message': 'No change on the provision parameters'})
def send(self, report): request = PDServerRequest('/api/routers/{router_id}') d = request.patch(*report) # Check for error code and retry. def cbresponse(response): if not response.success: out.warn('{} to {} returned code {}'.format(request.method, request.url, response.code)) if self.max_retries is None or self.retries < self.max_retries: reactor.callLater(self.retryDelay, self.send, report) self.retries += 1 self.increaseDelay() nexus.core.jwt_valid = False else: nexus.core.jwt_valid = True # Check for connection failures and retry. def cberror(ignored): out.warn('{} to {} failed'.format(request.method, request.url)) if self.max_retries is None or self.retries < self.max_retries: reactor.callLater(self.retryDelay, self.send, report) self.retries += 1 self.increaseDelay() nexus.core.jwt_valid = False d.addCallback(cbresponse) d.addErrback(cberror) return d
def verify_cloud_token(token): headers = {"Authorization": "Bearer {}".format(token)} # Pass custom Authorization header and setAuthHeader=False to prevent # PDServerRequest from using the node's own authorization token. request = PDServerRequest("/api/users/me", headers=headers, setAuthHeader=False) return request.get()
def auth_cloud(self, request): """ Login using credentials from the cloud controller. This is an experimental new login method that lets users present a token that they received from the cloud controller as a login credential for a node. The idea is to enable easy access for multiple developers to share a node, for example, during a tutorial. Instead of a username/password, the user presents a token received from the cloud controller. The verify_cloud_token function verifies the validity of the token with the controller, and if successful, retrieves information about the bearer, particularly the username and role. Finally, we generate a new token that enables the user to authenticate with local API endpoints. """ cors.config_cors(request) request.setHeader('Content-Type', 'application/json') body = json.loads(request.content.read()) token = body.get('token', '') headers = {"Authorization": "Bearer {}".format(token)} node_id = nexus.core.info.pdid # Pass custom Authorization header and setAuthHeader=False to prevent # PDServerRequest from using the node's own authorization token. d1 = PDServerRequest("/api/users/me", headers=headers, setAuthHeader=False).get() d2 = PDServerRequest("/api/routers/{}".format(node_id), headers=headers, setAuthHeader=False).get() def token_verified(responses): for response in responses: if not response.success or response.data is None: request.setResponseCode(401) return remote_user = responses[0].data node_info = responses[1].data role = get_access_level(remote_user, node_info) token = self.token_manager.issue(remote_user['email'], domain="paradrop.org", role=role) result = {'token': token, 'username': remote_user['email']} return json.dumps(result) d = defer.gatherResults([d1, d2]) d.addCallback(token_verified) return d
def verify_cloud_token(token): headers = { "Authorization": "Bearer {}".format(token) } # Pass custom Authorization header and setAuthHeader=False to prevent # PDServerRequest from using the node's own authorization token. request = PDServerRequest("/api/users/me", headers=headers, setAuthHeader=False) return request.get()
def _update_ignored(self, update): """ Internal: callback for an update that we are ignoring because it was started previously and never completed. """ update_id = update['_id'] request = PDServerRequest('/api/routers/{router_id}/updates/' + str(update_id)) d = request.patch( {'op': 'replace', 'path': '/completed', 'value': True}, {'op': 'replace', 'path': '/success', 'value': False} ) return d
def start_long_poll(self): self.long_poll_started = True while self.use_long_poll: request = PDServerRequest('/api/routers/{router_id}/updates/poll') try: response = yield request.get() except Exception: pass else: # 200 = update(s) available # 204 = no updates yet # 404 = server does not support this endpoint if response.code == 200: self.pull_update(_auto=False) elif response.code == 404: self.use_long_poll = False
def progress(self, message): if self.pkg is not None: self.pkg.request.write(message + '\n') # TODO Look into this. # This might happen during router initialization. If nexus.core is # None, we do not know the router's identity, so we cannot publish any # messages. if nexus.core is None: return data = {'time': time.time(), 'message': message} def handleError(error): print("Error sending message: {}".format(error.getErrorMessage())) # The external field is set for updates from pdserver but not for # locally-initiated (sideloaded) updates. update_id = None if hasattr(self, 'external'): update_id = self.external['update_id'] request = PDServerRequest( '/api/routers/{}/updates/{}/messages'.format( nexus.core.info.pdid, update_id)) d = request.post(**data) d.addErrback(handleError) session = getattr(nexus.core, 'session', None) if session is not None: data['update_id'] = update_id # Catch the occasional Exception due to connectivity failure. We # don't want to fail a chute installation just because we had problems # sending the log messages. try: session.publish(session.uriPrefix + 'updateProgress', data) except Exception as error: out.warn("Publish failed: {} {}".format( error.__class__, error)) # Send messages to internal consumers (e.g. open websocket connections) self.messages.append(data) for observer in self.message_observers: observer.on_message(data)
def started(self): """ This function should be called when the updated object is dequeued and execution is about to begin. Sends a notification to the pdserver if this is a tracked update. """ # TODO Look into this. # This might happen during router initialization. If nexus.core is # None, we do not know the router's identity, so we cannot publish any # messages. if nexus.core is None: return # The external field is set for updates from pdserver but not for # locally-initiated (sideloaded) updates. if hasattr(self, 'external'): update_id = self.external['update_id'] request = PDServerRequest('/api/routers/{router_id}/updates/' + str(update_id)) request.patch({'op': 'replace', 'path': '/started', 'value': True})