def build_connection(self, peer=None, address=None, identity=None, publickey=None, secretkey=None, serverkey=None, capabilities=[], **kwargs): self.logit('Building connection to {}'.format(peer)) self.allow_all_connections() if address is None: self.logit( 'Default address was None so setting to current instances') address = self.vip_address serverkey = self.serverkey if serverkey is None: self.logit("serverkey wasn't set but the address was.") raise Exception("Invalid state.") if publickey is None or secretkey is None: self.logit('generating new public secret key pair') keyfile = tempfile.mktemp(".keys", "agent", self.volttron_home) keys = KeyStore(keyfile) keys.generate() publickey = keys.public secretkey = keys.secret entry = AuthEntry(capabilities=capabilities, comments="Added by test", credentials=keys.public) file = AuthFile(self.volttron_home + "/auth.json") file.add(entry) conn = Connection(address=address, peer=peer, publickey=publickey, secretkey=secretkey, serverkey=serverkey, volttron_home=self.volttron_home) return conn
def start_wrapper_platform(wrapper, with_http=False, with_tcp=True, volttron_central_address=None, volttron_central_serverkey=None, add_local_vc_address=False): """ Customize easily customize the platform wrapper before starting it. """ assert not wrapper.is_running() vc_http = get_rand_http_address() if with_http else None vc_tcp = get_rand_tcp_address() if with_tcp else None if add_local_vc_address: ks = KeyStore(os.path.join(wrapper.volttron_home, 'keystore')) ks.generate() volttron_central_address = vc_tcp volttron_central_serverkey = ks.public wrapper.startup_platform( vip_address=vc_tcp, bind_web_address=vc_http, volttron_central_address=volttron_central_address, volttron_central_serverkey=volttron_central_serverkey) if with_http: discovery = "{}/discovery/".format(vc_http) response = requests.get(discovery) assert response.ok assert wrapper.is_running()
def setup_control_connection(request, get_volttron_instances): """ Creates a single instance of VOLTTRON for testing purposes """ global wrapper, control_connection wrapper = get_volttron_instances(1) request.addfinalizer(wrapper.shutdown_platform) assert wrapper assert wrapper.is_running() if get_volttron_instances.param == 'encrypted': if wrapper.encrypt: wrapper.allow_all_connections() # Connect using keys ks = KeyStore() ks.generate() control_connection = build_connection(identity="foo", address=wrapper.vip_address, peer=CONTROL, serverkey=wrapper.serverkey, publickey=ks.public, secretkey=ks.secret) else: control_connection = Connection(address=wrapper.local_vip_address, peer=CONTROL, developer_mode=True) # Sleep a couple seconds to wait for things to startup gevent.sleep(2) return wrapper, control_connection
def setup_control_connection(request, get_volttron_instances): """ Creates a single instance of VOLTTRON for testing purposes """ global wrapper, control_connection wrapper = get_volttron_instances(1) request.addfinalizer(wrapper.shutdown_platform) assert wrapper assert wrapper.is_running() wrapper.allow_all_connections() # Connect using keys ks = KeyStore() ks.generate() control_connection = build_connection(identity="foo", address=wrapper.vip_address, peer=CONTROL, serverkey=wrapper.serverkey, publickey=ks.public, secretkey=ks.secret, instance_name=wrapper.instance_name, message_bus=wrapper.messagebus) # Sleep a couple seconds to wait for things to startup gevent.sleep(2) return wrapper, control_connection
def start_wrapper_platform(wrapper, with_http=False, with_tcp=True, volttron_central_address=None, volttron_central_serverkey=None, add_local_vc_address=False): """ Customize easily customize the platform wrapper before starting it. """ assert not wrapper.is_running() vc_http = get_rand_http_address() if with_http else None vc_tcp = get_rand_tcp_address() if with_tcp else None if add_local_vc_address: ks = KeyStore(os.path.join(wrapper.volttron_home, 'keystore')) ks.generate() volttron_central_address = vc_tcp volttron_central_serverkey = ks.public wrapper.startup_platform(vip_address=vc_tcp, bind_web_address=vc_http, volttron_central_address=volttron_central_address, volttron_central_serverkey=volttron_central_serverkey) if with_http: discovery = "{}/discovery/".format(vc_http) response = requests.get(discovery) assert response.ok assert wrapper.is_running()
def forwarder(request, volttron_instances): #print "Fixture forwarder" global volttron_instance1, volttron_instance2 global forwarder_uuid, forwarder_config # 1. Update destination address in forwarder configuration if volttron_instance1.encrypt: tf = tempfile.NamedTemporaryFile() ks = KeyStore(tf.name) # generate public private key pair for instance1 ks.generate() # add public key of instance1 to instance2 auth file authfile = AuthFile(volttron_instance2.volttron_home + "/auth.json") entry = AuthEntry(credentials=ks.public) authfile.add(entry) # setup destination address to include keys forwarder_config["destination-vip"] =\ "{}?serverkey={}&publickey={}&secretkey={}".format( volttron_instance2.vip_address, volttron_instance2.serverkey, ks.public, ks.secret) else: forwarder_config["destination-vip"] = volttron_instance2.vip_address # 1: Install historian agent # Install and start sqlhistorian agent in instance2 forwarder_uuid = volttron_instance1.install_agent( agent_dir="services/core/ForwardHistorian", config_file=forwarder_config, start=True) print("forwarder agent id: ", forwarder_uuid)
def build_agent(self, address=None, should_spawn=True, identity=None, publickey=None, secretkey=None, serverkey=None, generatekeys=False, **kwargs): """ Build an agent connnected to the passed bus. By default the current instance that this class wraps will be the vip address of the agent. :param address: :param should_spawn: :param identity: :param publickey: :param secretkey: :param serverkey: :return: """ self.logit("Building generic agent.") use_ipc = kwargs.pop('use_ipc', False) if address is None: if use_ipc: self.logit('Using IPC vip-address') address = "ipc://@"+self.volttron_home+"/run/vip.socket" else: self.logit('Using vip-address '+self.vip_address) address = self.vip_address if generatekeys: self.logit('generating new public secret key pair') tf = tempfile.NamedTemporaryFile() ks = KeyStore(tf.name) ks.generate() publickey = ks.public() secretkey = ks.secret() if publickey and not serverkey: self.logit('using instance serverkey: {}'.format(self.publickey)) serverkey = self.publickey agent = Agent(address=address, identity=identity, publickey=publickey, secretkey=secretkey, serverkey=serverkey, **kwargs) self.logit('platformwrapper.build_agent.address: {}'.format(address)) # Automatically add agent's credentials to auth.json file if publickey: self.logit('Adding publickey to auth.json') gevent.spawn(self._append_allow_curve_key, publickey) gevent.sleep(0.1) if should_spawn: self.logit('platformwrapper.build_agent spawning') event = gevent.event.Event() gevent.spawn(agent.core.run, event)#.join(0) event.wait(timeout=2) hello = agent.vip.hello().get(timeout=.3) self.logit('Got hello response {}'.format(hello)) return agent
def tcp_to(instance): tmp = tempfile.NamedTemporaryFile() key = KeyStore(tmp.name) key.generate() return "{}?serverkey={}&publickey={}&secretkey={}".format( instance.vip_address, instance.serverkey, key.public, key.secret)
def __init__(self): """ Initializes a new VOLTTRON instance Creates a temporary VOLTTRON_HOME directory with a packaged directory for agents that are built. """ self.volttron_home = tempfile.mkdtemp() self.packaged_dir = os.path.join(self.volttron_home, "packaged") os.makedirs(self.packaged_dir) # in the context of this platform it is very important not to # use the main os.environ for anything. self.env = { 'VOLTTRON_HOME': self.volttron_home, 'PACKAGED_DIR': self.packaged_dir, 'DEBUG_MODE': os.environ.get('DEBUG_MODE', ''), 'DEBUG': os.environ.get('DEBUG', ''), 'PATH': VOLTTRON_ROOT + ':' + os.environ['PATH'] } # By default no web server should be started. self.bind_web_address = None self.discovery_address = None self.jsonrpc_endpoint = None self.volttron_central_address = None self.instance_name = None self.serverkey = None self.p_process = None self.t_process = None self.started_agent_pids = [] self.local_vip_address = None self.vip_address = None self.encrypt = False self.logit('Creating platform wrapper') # This was used when we are testing the SMAP historian. self.use_twistd = False # Added restricted code properties self.certsobj = None # Control whether the instance directory is cleaned up when shutdown. # if the environment variable DEBUG is set to a True value then the # instance is not cleaned up. self.skip_cleanup = False # This is used as command line entry replacement. Especially working # with older 2.0 agents. self.opts = None keystorefile = os.path.join(self.volttron_home, 'keystore') self.keystore = KeyStore(keystorefile) self.keystore.generate()
def get_configs(config_id, output_directory): keystore = KeyStore() agent = Agent(address=get_address(), publickey=keystore.public, secretkey=keystore.secret, enable_store=False) event = gevent.event.Event() gevent.spawn(agent.core.run, event) event.wait() config_list = agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_list_configs', config_id).get(timeout=10) if not config_list: print "Config store", config_id, "does not exist." return ensure_dir(output_directory) os.chdir(output_directory) for config in config_list: print "Retrieving configuration", config raw_config = agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_get', config_id, config, raw=True).get(timeout=10) ensure_dir(os.path.dirname(config)) with open(config, "w") as f: f.write(raw_config)
def get_agent_keystore(self, agent_uuid, encoded_public=None, encoded_secret=None): agent_path = os.path.join(self.install_dir, agent_uuid) agent_name = self.agent_name(agent_uuid) dist_info = os.path.join(agent_path, agent_name, agent_name + '.dist-info') keystore_path = os.path.join(dist_info, 'keystore.json') return KeyStore(keystore_path, encoded_public, encoded_secret)
def _get_keys_from_keystore(self): '''Returns agent's public and secret key from keystore''' if self.agent_uuid: # this is an installed agent keystore_dir = os.curdir elif self.identity: if not self.volttron_home: raise ValueError('VOLTTRON_HOME must be specified.') keystore_dir = os.path.join(self.volttron_home, 'keystores', self.identity) if not os.path.exists(keystore_dir): os.makedirs(keystore_dir) else: # the agent is not installed and its identity was not set return None, None keystore_path = os.path.join(keystore_dir, 'keystore.json') keystore = KeyStore(keystore_path) return keystore.public(), keystore.secret()
def test_discovery(scheme): vhome = create_volttron_home() # creates a vhome level key store keystore = KeyStore() serverkey = decode_key(keystore.public) # Depending upon scheme we enable/disable password jwt and certificate based jwt. if scheme == 'https': with certs_profile_1('/'.join([vhome, 'certs'])) as certs: config_params = dict(web_ssl_key=certs.server_certs[0].key_file, web_ssl_cert=certs.server_certs[0].cert_file) else: config_params = dict(web_secret_key=get_random_key()) with get_test_volttron_home(messagebus='zmq', config_params=config_params): instance_name = "booballoon" host, port = get_hostname_and_random_port() # this is the vip address address = f"tcp://{host}:{port}" def _construct_query_mock(core): """ Internal function that creates a concrete response for the data. when query('instance-name').get() is called the passed instance name is returned """ nonlocal instance_name, address kv = {"instance-name": instance_name, "addresses": [address]} return MockQuery(**kv) with mock.patch('volttron.platform.vip.agent.subsystems.query.Query', _construct_query_mock): host, port = get_hostname_and_random_port() bind_web_address = f"{scheme}://{host}:{port}" serverkey = decode_key(keystore.public) mws = MasterWebService(serverkey=serverkey, identity=MASTER_WEB, address=address, bind_web_address=bind_web_address, **config_params) mws.startupagent(sender='testweb') env = get_test_web_env("/discovery/") mock_start_response = mock.Mock() # A closingiterator is returned from the response object so we use the next # on the returned response. Then we can do json responses. response = mws.app_routing(env, mock_start_response).__next__() # load json into a dict for testing responses. response = jsonapi.loads(response.decode('utf-8')) assert response.get('instance-name') is not None assert instance_name == response.get('instance-name') assert keystore.public == response.get('serverkey') assert address == response.get('vip-address')
def main(): # parse the command line arguments arg_parser = argparse.ArgumentParser(description=__doc__) arg_parser.add_argument( "--address", help="Target only device(s) at <address> for request") arg_parser.add_argument( "--range", type=int, nargs=2, metavar=('LOW', 'HIGH'), help="Lower and upper limit on device ID in results") arg_parser.add_argument( "--timeout", type=int, metavar=('SECONDS'), help="Time, in seconds, to wait for responses. Default: %(default)s", default=5) arg_parser.add_argument("--proxy-id", help="VIP IDENTITY of the BACnet proxy agent.", default="platform.bacnet_proxy") args = arg_parser.parse_args() _log.debug("initialization") _log.debug(" - args: %r", args) keystore = KeyStore() agent = BACnetInteraction(args.proxy_id, address=get_address(), volttron_home=get_home(), publickey=keystore.public, secretkey=keystore.secret, enable_store=False) event = gevent.event.Event() gevent.spawn(agent.core.run, event) event.wait() kwargs = {'address': args.address} if args.range is not None: kwargs['low_device_id'] = int(args.range[0]) kwargs['high_device_id'] = int(args.range[1]) try: agent.send_iam(**kwargs) except errors.Unreachable: _log.error( "There is no BACnet proxy Agent running on the platform with the VIP IDENTITY {}" .format(args.proxy_id)) else: gevent.sleep(args.timeout)
def build_agent_with_key(platform: PlatformWrapper, identity=None): """Create an agent instance that has a generated public and private key. The passed platform will be the vip-address of the agent and the identity will be set. If the identity is set to None then a random identity will be created. """ os.environ['VOLTTRON_HOME'] = platform.volttron_home keys = KeyStore(os.path.join(platform.volttron_home, identity + '.keys')) keys.generate() agent = platform.build_agent(identity=identity, serverkey=platform.publickey, publickey=keys.public, secretkey=keys.secret) # Make publickey easily accessible for these tests agent.publickey = keys.public gevent.sleep(0.1) # switch context for a bit os.environ.pop('VOLTTRON_HOME') return agent
def _get_keys_from_keystore(self): '''Returns agent's public and secret key from keystore''' if self.agent_uuid: # this is an installed agent keystore_dir = os.curdir elif self.identity: if not os.environ.get('VOLTTRON_HOME'): raise ValueError('VOLTTRON_HOME must be specified.') keystore_dir = os.path.join( os.environ.get('VOLTTRON_HOME'), 'keystores', self.identity) if not os.path.exists(keystore_dir): os.makedirs(keystore_dir) else: # the agent is not installed and its identity was not set return None, None keystore_path = os.path.join(keystore_dir, 'keystore.json') keystore = KeyStore(keystore_path) return keystore.public(), keystore.secret()
def get_server_keys(): try: # attempt to read server's keys. Should be used only by multiplatform connection and tests # If agents such as forwarder attempt this in secure mode this will throw access violation exception ks = KeyStore() except IOError as e: raise RuntimeError( "Exception accessing server keystore. Agents must use agent's public and private key" "to build dynamic agents when running in secure mode. Exception:{}" .format(e)) return ks.public, ks.secret
def build_connection(self, peer=None, address=None, identity=None, publickey=None, secretkey=None, serverkey=None, **kwargs): if self.encrypt: self.allow_all_connections() if address is None: address = self.vip_address serverkey = self.serverkey if publickey is None or secretkey is None: self.logit('generating new public secret key pair') keyfile = tempfile.mktemp(".keys", "agent", self.volttron_home) keys = KeyStore(keyfile) keys.generate() publickey = keys.public() secretkey = keys.secret() if self.encrypt: conn = Connection(address=address, peer=peer, publickey=publickey, secretkey=secretkey, serverkey=serverkey, volttron_home=self.volttron_home) else: conn = Connection(address=self.local_vip_address, peer=peer, volttron_home=self.volttron_home) return conn
def update_curve_key(curve_key_path, no_warn=False): try: with open(curve_key_path, 'r') as curve_file: public, secret = read_curve_key(curve_file) except IOError as e: print e return keystore_path = os.path.join(os.path.dirname(curve_key_path), 'keystore') if os.path.exists(keystore_path) and not no_warn: response = raw_input("{} already exists. " "Overwrite? [y/N]: ".format(keystore_path)) if not response.lower().startswith('y'): print "Key update aborted." return keystore = KeyStore(keystore_path) keystore.public = public keystore.secret = secret print "Keys from {} have been transfered to {}".format(curve_key_path, keystore.filename)
def get_keys(): """Gets keys from keystore and known-hosts store :returns: Keys for connecting to the platform :rtype: dict """ hosts = KnownHostsStore() serverkey = hosts.serverkey(get_address()) key_store = KeyStore() publickey = key_store.public secretkey = key_store.secret return {'publickey': publickey, 'secretkey': secretkey, 'serverkey': serverkey}
def install_configs(input_directory, keep=False): try: os.chdir(input_directory) except FileNotFoundError: print(f"'input_directory' could not be found: {input_directory}") return ks = KeyStore() agent = build_agent(identity=PLATFORM, publickey=ks.public, secretkey=ks.secret, enable_store=True, timeout=30) if not keep: print("Deleting old Platform Driver store") agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_delete_store', PLATFORM_DRIVER).get(timeout=10) with open("config") as f: print("Storing main configuration") agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_store', PLATFORM_DRIVER, 'config', f.read(), config_type="json").get(timeout=10) for name in glob.iglob("registry_configs/*"): with open(name) as f: print("Storing configuration:", name) agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_store', PLATFORM_DRIVER, name, f.read(), config_type="csv").get(timeout=10) for dir_path, _, files in os.walk("devices"): for file_name in files: name = os.path.join(dir_path, file_name) with open(name) as f: print("Storing configuration:", name) agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_store', PLATFORM_DRIVER, name, f.read(), config_type="json").get(timeout=10)
def install_configs(input_directory, keep=False): os.chdir(input_directory) keystore = KeyStore() agent = Agent(address=get_address(), identity="master_driver_update_agent", publickey=keystore.public, secretkey=keystore.secret, enable_store=False) event = gevent.event.Event() gevent.spawn(agent.core.run, event) event.wait() if not keep: print("Deleting old Master Driver store") agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_delete_store', PLATFORM_DRIVER).get(timeout=10) with open("config") as f: print("Storing main configuration") agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_store', PLATFORM_DRIVER, 'config', f.read(), config_type="json").get(timeout=10) for name in glob.iglob("registry_configs/*"): with open(name) as f: print("Storing configuration:", name) agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_store', PLATFORM_DRIVER, name, f.read(), config_type="csv").get(timeout=10) for dir_path, _, files in os.walk("devices"): for file_name in files: name = os.path.join(dir_path, file_name) with open(name) as f: print("Storing configuration:", name) agent.vip.rpc.call(CONFIGURATION_STORE, 'manage_store', PLATFORM_DRIVER, name, f.read(), config_type="json").get(timeout=10)
def _get_keys_from_keystore(self): '''Returns agent's public and secret key from keystore''' if self.agent_uuid: # this is an installed agent, put keystore in its dist-info current_directory = os.path.abspath(os.curdir) keystore_dir = os.path.join( current_directory, "{}.dist-info".format(os.path.basename(current_directory))) elif self.identity is None: raise ValueError("Agent's VIP identity is not set") else: if not self.volttron_home: raise ValueError('VOLTTRON_HOME must be specified.') keystore_dir = os.path.join(self.volttron_home, 'keystores', self.identity) keystore_path = os.path.join(keystore_dir, 'keystore.json') keystore = KeyStore(keystore_path) return keystore.public, keystore.secret
def upgrade_old_agents(aip): """ Moves any keystore.json from agent-data to dist-info. Only applies to agents in auth file. """ vhome = Path(aip.env.volttron_home) agent_map = aip.get_agent_identity_to_uuid_mapping() auth_file = AuthFile() install_dir = vhome.joinpath("agents") for agent in agent_map: agent_path = install_dir.joinpath(agent_map[agent]) try: agent_data = get_agent_path(agent_path, 'agent-data') # Skip if no agent-data exists except KeyError as err: print(f"agent-data not found for {err}") continue keystore_path = agent_data.joinpath('keystore.json') try: dist_info = get_agent_path(agent_path, 'dist-info') # Skip if no dist-info exists except KeyError as err: print(f"dist-info not found for {err}") continue keystore_dest_path = dist_info.joinpath('keystore.json') if keystore_path.exists(): agent_keystore = KeyStore(keystore_path) for entry in auth_file.read()[0]: # Only move if agent exists in auth file if entry.credentials == agent_keystore.public: shutil.move(str(keystore_path), str(keystore_dest_path)) break return
def build_agent_with_key(platform, identity=None): """Create an agent instance that has a generated public and private key. The passed platform will be the vip-address of the agent and the identity will be set. If the identity is set to None then a random identity will be created. """ keys = KeyStore(os.path.join(platform.volttron_home, identity + '.keys')) keys.generate() agent = platform.build_agent(identity=identity, serverkey=platform.publickey, publickey=keys.public(), secretkey=keys.secret()) # Make publickey easily accessible for these tests agent.publickey = keys.public() gevent.sleep(0.1) # switch context for a bit return agent
def main(): global agent global config_writer # parse the command line arguments arg_parser = argparse.ArgumentParser(description=__doc__) arg_parser.add_argument("device_id", type=int, help="Device ID of the target device") arg_parser.add_argument( "--address", help= "Address of target device, may be needed to help route initial request to device." ) arg_parser.add_argument("--registry-out-file", type=argparse.FileType('w'), help="Output registry to CSV file", default=sys.stdout) arg_parser.add_argument("--driver-out-file", type=argparse.FileType('w'), help="Output driver configuration to JSON file.", default=sys.stdout) arg_parser.add_argument( "--max-range-report", nargs='?', type=float, help= 'Affects how very large numbers are reported in the "Unit Details" column of the ' 'output. Does not affect driver behavior.', default=1.0e+20) arg_parser.add_argument("--proxy-id", help="VIP IDENTITY of the BACnet proxy agent.", default="platform.bacnet_proxy") args = arg_parser.parse_args() _log.debug("initialization") _log.debug(" - args: %r", args) key_store = KeyStore() config_writer = DictWriter( args.registry_out_file, ('Reference Point Name', 'Volttron Point Name', 'Units', 'Unit Details', 'BACnet Object Type', 'Property', 'Writable', 'Index', 'Write Priority', 'Notes')) config_writer.writeheader() agent = build_agent(address=get_address(), volttron_home=get_home(), publickey=key_store.public, secretkey=key_store.secret, enable_store=False) bn = BACnetReader(agent.vip, args.proxy_id, bacnet_response) async_result = AsyncResult() try: bn.get_iam(args.device_id, async_result.set, args.address) except errors.Unreachable as ure: _log.error(ure) _log.error( "No BACnet proxy Agent running on the platform with the VIP IDENTITY {}" .format(args.proxy_id)) sys.exit(1) try: results = async_result.get(timeout=5.0) except gevent.Timeout: _log.error("No response from device id {}".format(args.device_id)) sys.exit(1) if args.address and args.address != results["address"]: msg = "Inconsistent results from passed address ({}) and device address ({}) using results.".format( args.address, results["address"]) _log.warning(msg) args.address = results["address"] elif results["address"]: args.address = results["address"] bn.read_device_properties(target_address=args.address, device_id=args.device_id) agent.core.stop()
def connect_remote_platform( self, address, serverkey=None, agent_class=None ): """ Agent attempts to connect to a remote platform to exchange data. address must start with http, https, tcp, ampq, or ampqs or a ValueError will be raised If this function is successful it will return an instance of the `agent_class` parameter if not then this function will return None. If the address parameter begins with http or https TODO: use the known host functionality here the agent will attempt to use Discovery to find the values associated with it. Discovery should return either an rmq-address or a vip-address or both. In that situation the connection will be made using zmq. In the event that fails then rmq will be tried. If both fail then None is returned from this function. """ from volttron.platform.vip.agent.utils import build_agent from volttron.platform.vip.agent import Agent if agent_class is None: agent_class = Agent parsed_address = urlparse(address) _log.debug("Begining auth.connect_remote_platform: {}".format(address)) value = None if parsed_address.scheme == "tcp": # ZMQ connection hosts = KnownHostsStore() temp_serverkey = hosts.serverkey(address) if not temp_serverkey: _log.info( "Destination serverkey not found in known hosts file, " "using config" ) destination_serverkey = serverkey elif not serverkey: destination_serverkey = temp_serverkey else: if temp_serverkey != serverkey: raise ValueError( "server_key passed and known hosts serverkey do not " "" "match!" ) destination_serverkey = serverkey publickey, secretkey = ( self._core().publickey, self._core().secretkey, ) _log.debug( "Connecting using: %s", get_fq_identity(self._core().identity) ) value = build_agent( agent_class=agent_class, identity=get_fq_identity(self._core().identity), serverkey=destination_serverkey, publickey=publickey, secretkey=secretkey, message_bus="zmq", address=address, ) elif parsed_address.scheme in ("https", "http"): from volttron.platform.web import DiscoveryInfo from volttron.platform.web import DiscoveryError try: # TODO: Use known host instead of looking up for discovery # info if possible. # We need to discover which type of bus is at the other end. info = DiscoveryInfo.request_discovery_info(address) remote_identity = "{}.{}.{}".format( info.instance_name, get_platform_instance_name(), self._core().identity, ) # if the current message bus is zmq then we need # to connect a zmq on the remote, whether that be the # rmq router or proxy. Also note that we are using the # fully qualified # version of the identity because there will be conflicts if # volttron central has more than one platform.agent connecting if get_messagebus() == "zmq": if not info.vip_address or not info.serverkey: err = ( "Discovery from {} did not return serverkey " "and/or vip_address".format(address) ) raise ValueError(err) _log.debug( "Connecting using: %s", get_fq_identity(self._core().identity), ) # use fully qualified identity value = build_agent( identity=get_fq_identity(self._core().identity), address=info.vip_address, serverkey=info.serverkey, secretkey=self._core().secretkey, publickey=self._core().publickey, agent_class=agent_class, ) else: # we are on rmq messagebus # This is if both remote and local are rmq message buses. if info.messagebus_type == "rmq": _log.debug("Both remote and local are rmq messagebus.") fqid_local = get_fq_identity(self._core().identity) # Check if we already have the cert, if so use it # instead of requesting cert again remote_certs_dir = self.get_remote_certs_dir() remote_cert_name = "{}.{}".format( info.instance_name, fqid_local ) certfile = os.path.join( remote_certs_dir, remote_cert_name + ".crt" ) if os.path.exists(certfile): response = certfile else: response = self.request_cert( address, fqid_local, info ) if response is None: _log.error("there was no response from the server") value = None elif isinstance(response, tuple): if response[0] == "PENDING": _log.info( "Waiting for administrator to accept a " "CSR request." ) value = None # elif isinstance(response, dict): # response elif os.path.exists(response): # info = DiscoveryInfo.request_discovery_info( # address) # From the remote platforms perspective the # remote user name is # remoteinstance.localinstance.identity, # this is what we must # pass to the build_remote_connection_params # for a successful remote_rmq_user = get_fq_identity( fqid_local, info.instance_name ) _log.debug( "REMOTE RMQ USER IS: %s", remote_rmq_user ) remote_rmq_address = self._core().rmq_mgmt.build_remote_connection_param( remote_rmq_user, info.rmq_address, ssl_auth=True, cert_dir=self.get_remote_certs_dir(), ) value = build_agent( identity=fqid_local, address=remote_rmq_address, instance_name=info.instance_name, publickey=self._core().publickey, secretkey=self._core().secretkey, message_bus="rmq", enable_store=False, agent_class=agent_class, ) else: raise ValueError( "Unknown path through discovery process!" ) else: # TODO: cache the connection so we don't always have # to ping the server to connect. # This branch happens when the message bus is not # the same note # this writes to the agent-data directory of this # agent if the agent # is installed. if get_messagebus() == "rmq": if not os.path.exists("keystore.json"): with open("keystore.json", "w") as file_pointer: file_pointer.write( jsonapi.dumps( KeyStore.generate_keypair_dict() ) ) with open("keystore.json") as file_pointer: keypair = jsonapi.loads(file_pointer.read()) value = build_agent( agent_class=agent_class, identity=remote_identity, serverkey=info.serverkey, publickey=keypair.get("publickey"), secretkey=keypair.get("secretekey"), message_bus="zmq", address=info.vip_address, ) except DiscoveryError: _log.error( "Couldn't connect to %s or incorrect response returned " "response was %s", address, value, ) else: raise ValueError( "Invalid configuration found the address: {} has an invalid " "scheme".format(address) ) return value
def test_forwarding(volttron_instance1_encrypt, volttron_instance2_encrypt): global FORWARDER_CONFIG tf = tempfile.NamedTemporaryFile() tf2 = tempfile.NamedTemporaryFile() tf3 = tempfile.NamedTemporaryFile() ks = KeyStore(tf.name) ks.generate() ks2 = KeyStore(tf2.name) ks2.generate() ks3 = KeyStore(tf2.name) ks3.generate() wrap1 = volttron_instance1_encrypt wrap2 = volttron_instance2_encrypt authfile1 = AuthFile(wrap1.volttron_home+"/auth.json") entry1 = AuthEntry( credentials="CURVE:{}".format(ks3.public()) ) authfile1.add(entry1) authfile = AuthFile(wrap2.volttron_home+"/auth.json") entry = AuthEntry( credentials="CURVE:{}".format(ks.public())) authfile.add(entry) entry = AuthEntry( credentials="CURVE:{}".format(ks2.public())) authfile.add(entry) forward_to_vip = "{}?serverkey={}&publickey={}&secretkey={}".format( wrap2.vip_address, wrap2.publickey, ks.public(), ks.secret() ) FORWARDER_CONFIG["destination-vip"] = forward_to_vip forwarder_config = FORWARDER_CONFIG print("THE CONFIG = {}".format(forwarder_config)) wrap1.install_agent( agent_dir="services/core/ForwardHistorian", config_file=forwarder_config ) connect_to_wrap2 = "{}?serverkey={}&publickey={}&secretkey={}".format( wrap2.vip_address, wrap2.publickey, ks2.public(), ks2.secret() ) connect_to_wrap1 = "{}?serverkey={}&publickey={}&secretkey={}".format( wrap1.vip_address, wrap1.publickey, ks3.public(), ks3.secret() ) agent_connected1 = wrap1.build_agent(address=connect_to_wrap1) agent_connected2 = wrap2.build_agent(address=connect_to_wrap2) message = '' agent_connected2.vip.pubsub.subscribe('pubsub', '', callback=onmessage) gevent.sleep(0.2) do_publish(agent1=agent_connected1) gevent.sleep(1) assert allforwardedmessage
import os import sys import gevent from volttron.platform import get_address from volttron.platform.agent.known_identities import VOLTTRON_CENTRAL_PLATFORM from volttron.platform.keystore import KeyStore from volttron.platform.messaging import topics from volttron.platform.vip.agent import Agent keystore = KeyStore() agent = Agent(address=get_address(), identity="blahagent", publickey=keystore.public(), secretkey=keystore.secret(), enable_store=False) event = gevent.event.Event() config_store_task = gevent.spawn(agent.core.run, event) event.wait() del event if VOLTTRON_CENTRAL_PLATFORM not in agent.vip.peerlist().get(): agent.core.stop() print('no vcp availablel') sys.exit() def receive_platform_data(peer, sender, bus, topic, headers, message): #assert 'message' in kwargs
def build_agent(self, address=None, should_spawn=True, identity=None, publickey=None, secretkey=None, serverkey=None, agent_class=Agent, **kwargs): """ Build an agent connnected to the passed bus. By default the current instance that this class wraps will be the vip address of the agent. :param address: :param should_spawn: :param identity: :param publickey: :param secretkey: :param serverkey: :param agent_class: Agent class to build :return: """ self.logit("Building generic agent.") use_ipc = kwargs.pop('use_ipc', False) if serverkey is None: serverkey = self.serverkey if publickey is None: self.logit('generating new public secret key pair') keyfile = tempfile.mktemp(".keys", "agent", self.volttron_home) keys = KeyStore(keyfile) keys.generate() publickey = keys.public secretkey = keys.secret if address is None: self.logit('Using vip-address ' + self.vip_address) address = self.vip_address if publickey and not serverkey: self.logit('using instance serverkey: {}'.format(self.publickey)) serverkey = self.publickey agent = agent_class(address=address, identity=identity, publickey=publickey, secretkey=secretkey, serverkey=serverkey, volttron_home=self.volttron_home, **kwargs) self.logit('platformwrapper.build_agent.address: {}'.format(address)) # Automatically add agent's credentials to auth.json file if publickey: self.logit('Adding publickey to auth.json') gevent.spawn(self._append_allow_curve_key, publickey) gevent.sleep(0.1) if should_spawn: self.logit('platformwrapper.build_agent spawning') event = gevent.event.Event() gevent.spawn(agent.core.run, event) # .join(0) event.wait(timeout=2) hello = agent.vip.hello().get(timeout=.3) self.logit('Got hello response {}'.format(hello)) agent.publickey = publickey return agent
def get_new_keypair(): tf = tempfile.NamedTemporaryFile() ks = KeyStore(tf.name) ks.generate() return ks.public, ks.secret
def __init__(self): """ Initializes a new VOLTTRON instance Creates a temporary VOLTTRON_HOME directory with a packaged directory for agents that are built. """ # This is hopefully going to keep us from attempting to shutdown # multiple times. For example if a fixture calls shutdown and a # lower level fixture calls shutdown, this won't hang. self._instance_shutdown = False self.volttron_home = tempfile.mkdtemp() self.packaged_dir = os.path.join(self.volttron_home, "packaged") os.makedirs(self.packaged_dir) # in the context of this platform it is very important not to # use the main os.environ for anything. self.env = { 'VOLTTRON_HOME': self.volttron_home, 'PACKAGED_DIR': self.packaged_dir, 'DEBUG_MODE': os.environ.get('DEBUG_MODE', ''), 'DEBUG': os.environ.get('DEBUG', ''), 'PATH': VOLTTRON_ROOT + ':' + os.environ['PATH'] } self.volttron_root = VOLTTRON_ROOT volttron_exe = subprocess.check_output(['which', 'volttron']).strip() assert os.path.exists(volttron_exe) self.python = os.path.join(os.path.dirname(volttron_exe), 'python') assert os.path.exists(self.python) # By default no web server should be started. self.bind_web_address = None self.discovery_address = None self.jsonrpc_endpoint = None self.volttron_central_address = None self.instance_name = None self.serverkey = None self.p_process = None self.t_process = None self.started_agent_pids = [] self.local_vip_address = None self.vip_address = None self.logit('Creating platform wrapper') # This was used when we are testing the SMAP historian. self.use_twistd = False # Added restricted code properties self.certsobj = None # Control whether the instance directory is cleaned up when shutdown. # if the environment variable DEBUG is set to a True value then the # instance is not cleaned up. self.skip_cleanup = False # This is used as command line entry replacement. Especially working # with older 2.0 agents. self.opts = None keystorefile = os.path.join(self.volttron_home, 'keystore') self.keystore = KeyStore(keystorefile) self.keystore.generate()
class PlatformWrapper: def __init__(self): """ Initializes a new VOLTTRON instance Creates a temporary VOLTTRON_HOME directory with a packaged directory for agents that are built. """ # This is hopefully going to keep us from attempting to shutdown # multiple times. For example if a fixture calls shutdown and a # lower level fixture calls shutdown, this won't hang. self._instance_shutdown = False self.volttron_home = tempfile.mkdtemp() self.packaged_dir = os.path.join(self.volttron_home, "packaged") os.makedirs(self.packaged_dir) # in the context of this platform it is very important not to # use the main os.environ for anything. self.env = { 'VOLTTRON_HOME': self.volttron_home, 'PACKAGED_DIR': self.packaged_dir, 'DEBUG_MODE': os.environ.get('DEBUG_MODE', ''), 'DEBUG': os.environ.get('DEBUG', ''), 'PATH': VOLTTRON_ROOT + ':' + os.environ['PATH'] } self.volttron_root = VOLTTRON_ROOT volttron_exe = subprocess.check_output(['which', 'volttron']).strip() assert os.path.exists(volttron_exe) self.python = os.path.join(os.path.dirname(volttron_exe), 'python') assert os.path.exists(self.python) # By default no web server should be started. self.bind_web_address = None self.discovery_address = None self.jsonrpc_endpoint = None self.volttron_central_address = None self.instance_name = None self.serverkey = None self.p_process = None self.t_process = None self.started_agent_pids = [] self.local_vip_address = None self.vip_address = None self.logit('Creating platform wrapper') # This was used when we are testing the SMAP historian. self.use_twistd = False # Added restricted code properties self.certsobj = None # Control whether the instance directory is cleaned up when shutdown. # if the environment variable DEBUG is set to a True value then the # instance is not cleaned up. self.skip_cleanup = False # This is used as command line entry replacement. Especially working # with older 2.0 agents. self.opts = None keystorefile = os.path.join(self.volttron_home, 'keystore') self.keystore = KeyStore(keystorefile) self.keystore.generate() def logit(self, message): print('{}: {}'.format(self.volttron_home, message)) def allow_all_connections(self): """ Add a /.*/ entry to the auth.json file. """ entry = AuthEntry(credentials="/.*/") authfile = AuthFile(self.volttron_home + "/auth.json") try: authfile.add(entry) except AuthFileEntryAlreadyExists: pass def build_connection(self, peer=None, address=None, identity=None, publickey=None, secretkey=None, serverkey=None, capabilities=[], **kwargs): self.logit('Building connection to {}'.format(peer)) self.allow_all_connections() if address is None: self.logit( 'Default address was None so setting to current instances') address = self.vip_address serverkey = self.serverkey if serverkey is None: self.logit("serverkey wasn't set but the address was.") raise Exception("Invalid state.") if publickey is None or secretkey is None: self.logit('generating new public secret key pair') keyfile = tempfile.mktemp(".keys", "agent", self.volttron_home) keys = KeyStore(keyfile) keys.generate() publickey = keys.public secretkey = keys.secret entry = AuthEntry(capabilities=capabilities, comments="Added by test", credentials=keys.public) file = AuthFile(self.volttron_home + "/auth.json") file.add(entry) conn = Connection(address=address, peer=peer, publickey=publickey, secretkey=secretkey, serverkey=serverkey, volttron_home=self.volttron_home) return conn def build_agent(self, address=None, should_spawn=True, identity=None, publickey=None, secretkey=None, serverkey=None, agent_class=Agent, **kwargs): """ Build an agent connnected to the passed bus. By default the current instance that this class wraps will be the vip address of the agent. :param address: :param should_spawn: :param identity: :param publickey: :param secretkey: :param serverkey: :param agent_class: Agent class to build :return: """ self.logit("Building generic agent.") use_ipc = kwargs.pop('use_ipc', False) if serverkey is None: serverkey = self.serverkey if publickey is None: self.logit('generating new public secret key pair') keyfile = tempfile.mktemp(".keys", "agent", self.volttron_home) keys = KeyStore(keyfile) keys.generate() publickey = keys.public secretkey = keys.secret if address is None: self.logit('Using vip-address ' + self.vip_address) address = self.vip_address if publickey and not serverkey: self.logit('using instance serverkey: {}'.format(self.publickey)) serverkey = self.publickey agent = agent_class(address=address, identity=identity, publickey=publickey, secretkey=secretkey, serverkey=serverkey, volttron_home=self.volttron_home, **kwargs) self.logit('platformwrapper.build_agent.address: {}'.format(address)) # Automatically add agent's credentials to auth.json file if publickey: self.logit('Adding publickey to auth.json') gevent.spawn(self._append_allow_curve_key, publickey) gevent.sleep(0.1) if should_spawn: self.logit('platformwrapper.build_agent spawning') event = gevent.event.Event() gevent.spawn(agent.core.run, event) # .join(0) event.wait(timeout=2) hello = agent.vip.hello().get(timeout=.3) self.logit('Got hello response {}'.format(hello)) agent.publickey = publickey return agent def _read_auth_file(self): auth_path = os.path.join(self.volttron_home, 'auth.json') try: with open(auth_path, 'r') as fd: data = strip_comments(FileObject(fd, close=False).read()) if data: auth = jsonapi.loads(data) else: auth = {} except IOError: auth = {} if 'allow' not in auth: auth['allow'] = [] return auth, auth_path def _append_allow_curve_key(self, publickey): entry = AuthEntry(credentials=publickey) authfile = AuthFile(self.volttron_home + "/auth.json") try: authfile.add(entry) except AuthFileEntryAlreadyExists: pass def add_vc(self): return add_vc_to_instance(self) def add_capabilities(self, publickey, capabilities): if isinstance(capabilities, basestring): capabilities = [capabilities] auth, auth_path = self._read_auth_file() cred = publickey allow = auth['allow'] entry = next((item for item in allow if item['credentials'] == cred), {}) caps = entry.get('capabilities', []) entry['capabilities'] = list(set(caps + capabilities)) with open(auth_path, 'w+') as fd: json.dump(auth, fd) def set_auth_dict(self, auth_dict): if auth_dict: with open(os.path.join(self.volttron_home, 'auth.json'), 'w') as fd: fd.write(json.dumps(auth_dict)) def startup_platform(self, vip_address, auth_dict=None, use_twistd=False, mode=UNRESTRICTED, bind_web_address=None, volttron_central_address=None, volttron_central_serverkey=None): # if not isinstance(vip_address, list): # self.vip_address = [vip_address] # else: # self.vip_address = vip_address self.vip_address = vip_address self.mode = mode self.bind_web_address = bind_web_address if self.bind_web_address: self.discovery_address = "{}/discovery/".format( self.bind_web_address) # Only available if vc is installed! self.jsonrpc_endpoint = "{}/jsonrpc".format( self.bind_web_address) enable_logging = self.env.get('ENABLE_LOGGING', False) debug_mode = self.env.get('DEBUG_MODE', False) if not debug_mode: debug_mode = self.env.get('DEBUG', False) self.skip_cleanup = self.env.get('SKIP_CLEANUP', False) if debug_mode: self.skip_cleanup = True enable_logging = True self.logit( "In start up platform enable_logging is {} ".format(enable_logging)) assert self.mode in MODES, 'Invalid platform mode set: ' + str(mode) opts = None # see main.py for how we handle pub sub addresses. ipc = 'ipc://{}{}/run/'.format( '@' if sys.platform.startswith('linux') else '', self.volttron_home) self.local_vip_address = ipc + 'vip.socket' self.set_auth_dict(auth_dict) self.opts = {'verify_agents': False, 'volttron_home': self.volttron_home, 'vip_address': vip_address, 'vip_local_address': ipc + 'vip.socket', 'publish_address': ipc + 'publish', 'subscribe_address': ipc + 'subscribe', 'bind_web_address': bind_web_address, 'volttron_central_address': volttron_central_address, 'volttron_central_serverkey': volttron_central_serverkey, 'platform_name': None, 'log': os.path.join(self.volttron_home, 'volttron.log'), 'log_config': None, 'monitor': True, 'autostart': True, 'log_level': logging.DEBUG, 'verboseness': logging.DEBUG} pconfig = os.path.join(self.volttron_home, 'config') config = {} # Add platform's public key to known hosts file publickey = self.keystore.public known_hosts_file = os.path.join(self.volttron_home, 'known_hosts') known_hosts = KnownHostsStore(known_hosts_file) known_hosts.add(self.opts['vip_local_address'], publickey) known_hosts.add(self.opts['vip_address'], publickey) # Set up the configuration file based upon the passed parameters. parser = configparser.ConfigParser() parser.add_section('volttron') parser.set('volttron', 'vip-address', vip_address) if bind_web_address: parser.set('volttron', 'bind-web-address', bind_web_address) if volttron_central_address: parser.set('volttron', 'volttron-central-address', volttron_central_address) if volttron_central_serverkey: parser.set('volttron', 'volttron-central-serverkey', volttron_central_serverkey) if self.mode == UNRESTRICTED: # TODO Restricted code should set with volttron as contianer # if RESTRICTED_AVAILABLE: # config['mobility'] = False # config['resource-monitor'] = False # config['verify'] = False with closing(open(pconfig, 'wb')) as cfg: cfg.write(PLATFORM_CONFIG_UNRESTRICTED.format(**config)) parser.write(cfg) elif self.mode == RESTRICTED: if not RESTRICTED_AVAILABLE: raise ValueError("restricted is not available.") certsdir = os.path.join(self.volttron_home, 'certificates') print ("certsdir", certsdir) self.certsobj = certs.Certs(certsdir) with closing(open(pconfig, 'wb')) as cfg: cfg.write(PLATFORM_CONFIG_RESTRICTED.format(**config)) else: raise PlatformWrapperError( "Invalid platform mode specified: {}".format(mode)) log = os.path.join(self.volttron_home, 'volttron.log') if enable_logging: cmd = ['volttron', '-vv', '-l{}'.format(log)] else: cmd = ['volttron', '-l{}'.format(log)] print('process environment: {}'.format(self.env)) print('popen params: {}'.format(cmd)) self.p_process = Popen(cmd, env=self.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert self.p_process is not None # A None value means that the process is still running. # A negative means that the process exited with an error. assert self.p_process.poll() is None self.serverkey = self.keystore.public assert self.serverkey agent = self.build_agent() has_control = False times = 0 while not has_control and times < 10: times += 1 try: has_control = agent.vip.peerlist().get(timeout=.2) except gevent.Timeout: pass if not has_control: self.shutdown_platform() raise "Couldn't connect to core platform!" if bind_web_address: times = 0 has_discovery = False while times < 10: times += 1 try: resp = requests.get(self.discovery_address) if resp.ok: has_discovery = True break except Exception as e: gevent.sleep(0.1) self.logit("Connection error found {}".format(e)) if not has_discovery: raise "Couldn't connect to discovery platform." self.use_twistd = use_twistd # TODO: Revise this to start twistd with platform. if self.use_twistd: tconfig = os.path.join(self.volttron_home, TMP_SMAP_CONFIG_FILENAME) with closing(open(tconfig, 'w')) as cfg: cfg.write(TWISTED_CONFIG.format(**config)) tparams = [TWISTED_START, "-n", "smap", tconfig] self.t_process = subprocess.Popen(tparams, env=self.env) time.sleep(5) def is_running(self): self.logit("PROCESS IS RUNNING: {}".format(self.p_process)) return self.p_process is not None and self.p_process.poll() is None def twistd_is_running(self): return self.t_process is not None def direct_sign_agentpackage_creator(self, package): assert (RESTRICTED), "Auth not available" print ("wrapper.certsobj", self.certsobj.cert_dir) assert ( auth.sign_as_creator(package, 'creator', certsobj=self.certsobj)), "Signing as {} failed.".format( 'creator') def direct_sign_agentpackage_admin(self, package): assert (RESTRICTED), "Auth not available" assert (auth.sign_as_admin(package, 'admin', certsobj=self.certsobj)), "Signing as {} failed.".format( 'admin') def direct_sign_agentpackage_initiator(self, package, config_file, contract): assert (RESTRICTED), "Auth not available" files = {"config_file": config_file, "contract": contract} assert (auth.sign_as_initiator(package, 'initiator', files=files, certsobj=self.certsobj)), "Signing as {} failed.".format( 'initiator') def _aip(self): opts = type('Options', (), self.opts) aip = AIPplatform(opts) aip.setup() return aip def _install_agent(self, wheel_file, start, vip_identity): self.logit('Creating channel for sending the agent.') gevent.sleep(0.3) self.logit('calling control install agent.') self.logit("VOLTTRON_HOME SETTING: {}".format( self.env['VOLTTRON_HOME'])) env = self.env.copy() cmd = ['volttron-ctl', '-vv', 'install', wheel_file] if vip_identity: cmd.extend(['--vip-identity', vip_identity]) self.logit("cmd: {}".format(cmd)) res = subprocess.check_output(cmd, env=env) assert res, "failed to install wheel:{}".format(wheel_file) agent_uuid = res.split(' ')[-2] self.logit(agent_uuid) if start: self.start_agent(agent_uuid) return agent_uuid def install_multiple_agents(self, agent_configs): """ Installs mutltiple agents on the platform. :param agent_configs:list A list of 3-tuple that allows the configuration of a platform in a single go. The tuple order is 1. path to the agent directory. 2. configuration data (either file or json data) 3. Whether the agent should be started or not. :return:list: A list of uuid's associated with the agents that were installed. :Note: In order for this method to be called the platform must be currently running. """ if not self.is_running(): raise PlatformWrapperError("Instance isn't running!") results = [] for path, config, start in agent_configs: results = self.install_agent(agent_dir=path, config_file=config, start=start) return results def install_agent(self, agent_wheel=None, agent_dir=None, config_file=None, start=True, vip_identity=None): """ Install and optionally start an agent on the instance. This function allows installation from an agent wheel or an agent directory (NOT BOTH). If an agent_wheel is specified then it is assumed to be ready for installation (has a config file). If an agent_dir is specified then a config_file file must be specified or if it is not specified then it is assumed that the file agent_dir/config is to be used as the configuration file. If none of these exist then an assertion error will be thrown. This function will return with a uuid of the installed agent. :param agent_wheel: :param agent_dir: :param config_file: :param start: :param vip_identity: :return: """ assert self.is_running(), "Instance must be running to install agent." assert agent_wheel or agent_dir, "Invalid agent_wheel or agent_dir." if agent_wheel: assert not agent_dir assert not config_file assert os.path.exists(agent_wheel) wheel_file = agent_wheel agent_uuid = self._install_agent(wheel_file, start, vip_identity) # Now if the agent_dir is specified. if agent_dir: assert not agent_wheel if isinstance(config_file, dict): from os.path import join, basename temp_config = join(self.volttron_home, basename(agent_dir) + "_config_file") with open(temp_config, "w") as fp: fp.write(json.dumps(config_file)) config_file = temp_config elif not config_file: if os.path.exists(os.path.join(agent_dir, "config")): config_file = os.path.join(agent_dir, "config") else: from os.path import join, basename temp_config = join(self.volttron_home, basename(agent_dir) + "_config_file") with open(temp_config, "w") as fp: fp.write(json.dumps({})) config_file = temp_config elif os.path.exists(config_file): pass # config_file already set! else: raise ValueError("Can't determine correct config file.") script = os.path.join(self.volttron_root, "scripts/install-agent.py") cmd = [self.python, script, "--volttron-home", self.volttron_home, "--volttron-root", self.volttron_root, "--agent-source", agent_dir, "--config", config_file, "--json"] if vip_identity: cmd.extend(["--vip-identity", vip_identity]) if start: cmd.extend(["--start"]) results = subprocess.check_output(cmd) # Because we are no longer silencing output from the install, the # the results object is now much more verbose. Our assumption is # the line before the output we care about has WHEEL at the end # of it. new_results = "" found_wheel = False for line in results.split("\n"): if line.endswith("WHEEL"): found_wheel = True elif found_wheel: new_results += line results = new_results # # Response from results is expected as follows depending on # parameters, note this is a json string so parse to get dictionary. # { # "started": true, # "agent_pid": 26241, # "starting": true, # "agent_uuid": "ec1fd94e-922a-491f-9878-c392b24dbe50" # } assert results resultobj = jsonapi.loads(str(results)) if start: assert resultobj['started'] agent_uuid = resultobj['agent_uuid'] assert agent_uuid is not None if start: assert self.is_agent_running(agent_uuid) return agent_uuid def start_agent(self, agent_uuid): self.logit('Starting agent {}'.format(agent_uuid)) self.logit("VOLTTRON_HOME SETTING: {}".format( self.env['VOLTTRON_HOME'])) cmd = ['volttron-ctl'] cmd.extend(['start', agent_uuid]) p = Popen(cmd, env=self.env, stdout=sys.stdout, stderr=sys.stderr) p.wait() # Confirm agent running cmd = ['volttron-ctl'] cmd.extend(['status', agent_uuid]) res = subprocess.check_output(cmd, env=self.env) # 776 TODO: Timing issue where check fails time.sleep(.1) self.logit("Subprocess res is {}".format(res)) assert 'running' in res pidpos = res.index('[') + 1 pidend = res.index(']') pid = int(res[pidpos: pidend]) assert psutil.pid_exists(pid), \ "The pid associated with agent {} does not exist".format(pid) self.started_agent_pids.append(pid) return pid def stop_agent(self, agent_uuid): # Confirm agent running _log.debug("STOPPING AGENT: {}".format(agent_uuid)) try: cmd = ['volttron-ctl'] cmd.extend(['stop', agent_uuid]) res = subprocess.check_output(cmd, env=self.env) except CalledProcessError as ex: _log.error("Exception: {}".format(ex)) return self.agent_pid(agent_uuid) def list_agents(self): agent = self.build_agent() print('PEER LIST: {}'.format(agent.vip.peerlist().get(timeout=10))) agent_list = agent.vip.rpc('control', 'list_agents').get(timeout=10) agent.core.stop(timeout=3) return agent_list def remove_agent(self, agent_uuid): """Remove the agent specified by agent_uuid""" _log.debug("REMOVING AGENT: {}".format(agent_uuid)) try: cmd = ['volttron-ctl'] cmd.extend(['remove', agent_uuid]) res = subprocess.check_output(cmd, env=self.env) except CalledProcessError as ex: _log.error("Exception: {}".format(ex)) return self.agent_pid(agent_uuid) def is_agent_running(self, agent_uuid): return self.agent_pid(agent_uuid) is not None def agent_pid(self, agent_uuid): """ Returns the pid of a running agent or None :param agent_uuid: :return: """ # Confirm agent running cmd = ['volttron-ctl'] cmd.extend(['status', agent_uuid]) pid = None try: res = subprocess.check_output(cmd, env=self.env) try: pidpos = res.index('[') + 1 pidend = res.index(']') pid = int(res[pidpos: pidend]) except: pid = None except CalledProcessError as ex: _log.error("Exception: {}".format(ex)) # Handle the following exception that seems to happen when getting a # pid of an agent during the platform shutdown phase. # # Logged from file platformwrapper.py, line 797 # AGENT IDENTITY TAG STATUS # Traceback (most recent call last): # File "/usr/lib/python2.7/logging/__init__.py", line 882, in emit # stream.write(fs % msg) # File "/home/volttron/git/volttron/env/local/lib/python2.7/site-packages/_pytest/capture.py", line 244, in write # self.buffer.write(obj) # ValueError: I/O operation on closed file except ValueError: pass # _log.debug("AGENT_PID: {}".format(pid)) return pid def build_agentpackage(self, agent_dir, config_file={}): if isinstance(config_file, dict): cfg_path = os.path.join(agent_dir, "config_temp") with open(cfg_path, "w") as tmp_cfg: tmp_cfg.write(jsonapi.dumps(config_file)) config_file = cfg_path # Handle relative paths from the volttron git directory. if not os.path.isabs(agent_dir): agent_dir = os.path.join(self.volttron_root, agent_dir) assert os.path.exists(config_file) assert os.path.exists(agent_dir) wheel_path = packaging.create_package(agent_dir, self.packaged_dir) packaging.add_files_to_package(wheel_path, { 'config_file': os.path.join('./', config_file) }) return wheel_path def confirm_agent_running(self, agent_name, max_retries=5, timeout_seconds=2): running = False retries = 0 while not running and retries < max_retries: status = self.test_aip.status_agents() print ("Status", status) if len(status) > 0: status_name = status[0][1] assert status_name == agent_name assert len(status[0][2]) == 2, 'Unexpected agent status message' status_agent_status = status[0][2][1] running = not isinstance(status_agent_status, int) retries += 1 time.sleep(timeout_seconds) return running # def direct_stop_agent(self, agent_uuid): # result = self.conn.call.stop_agent(agent_uuid) # print result def shutdown_platform(self): """ Stop platform here. First grab a list of all of the agents that are running on the platform, then shutdown, then if any of the listed agent pids are still running then kill them. """ # Handle cascading calls from multiple levels of fixtures. if self._instance_shutdown: return running_pids = [] for agnt in self.list_agents(): pid = self.agent_pid(agnt['uuid']) if pid is not None and int(pid) > 0: running_pids.append(int(pid)) # First try and nicely shutdown the platform, which should clean all # of the agents up automatically. cmd = ['volttron-ctl'] cmd.extend(['shutdown', '--platform']) try: res = subprocess.check_output(cmd, env=self.env) except CalledProcessError: if self.p_process is not None: try: gevent.sleep(0.2) self.p_process.terminate() gevent.sleep(0.2) except OSError: self.logit('Platform process was terminated.') else: self.logit("platform process was null") for pid in running_pids: if psutil.pid_exists(pid): self.logit("TERMINATING: {}".format(pid)) proc = psutil.Process(pid) proc.terminate() if self.use_twistd and self.t_process is not None: self.t_process.kill() self.t_process.wait() elif self.use_twistd: self.logit("twistd process was null") if os.environ.get('PRINT_LOG'): logpath = os.path.join(self.volttron_home, 'volttron.log') if os.path.exists(logpath): print("************************* Begin {}".format(logpath)) with open(logpath) as f: for l in f.readlines(): print(l) print("************************* End {}".format(logpath)) else: print("######################### No Log Exists: {}".format( logpath )) if not self.skip_cleanup: self.logit('Removing {}'.format(self.volttron_home)) shutil.rmtree(self.volttron_home, ignore_errors=True) self._instance_shutdown = True def __repr__(self): return str(self) def __str__(self): data = [] data.append('volttron_home: {}'.format(self.volttron_home)) return '\n'.join(data)
def main(): global agent # parse the command line arguments arg_parser = argparse.ArgumentParser(description=__doc__) arg_parser.add_argument("device_id", type=int, help="Device ID of the target device") arg_parser.add_argument( "--address", help= "Address of target device, may be needed to help route initial request to device." ) arg_parser.add_argument("--registry-out-file", type=argparse.FileType('wb'), help="Output registry to CSV file", default=sys.stdout) arg_parser.add_argument("--driver-out-file", type=argparse.FileType('wb'), help="Output driver configuration to JSON file.", default=sys.stdout) arg_parser.add_argument( "--max-range-report", nargs='?', type=float, help= 'Affects how very large numbers are reported in the "Unit Details" column of the output. ' 'Does not affect driver behavior.', default=1.0e+20) arg_parser.add_argument("--proxy-id", help="VIP IDENTITY of the BACnet proxy agent.", default="platform.bacnet_proxy") args = arg_parser.parse_args() _log.debug("initialization") _log.debug(" - args: %r", args) key_store = KeyStore() agent = BACnetInteraction(args.proxy_id, address=get_address(), volttron_home=get_home(), publickey=key_store.public, secretkey=key_store.secret, enable_store=False) event = gevent.event.Event() gevent.spawn(agent.core.run, event) event.wait() async_result = AsyncResult() try: agent.get_iam(args.device_id, async_result.set, args.address) except errors.Unreachable: _log.error( "There is no BACnet proxy Agent running on the platform with the VIP IDENTITY {}" .format(args.proxy_id)) sys.exit(1) try: results = async_result.get(timeout=5.0) except gevent.Timeout: _log.error("No response from device id {}".format(args.device_id)) sys.exit(1) target_address = results["address"] device_id = results["device_id"] config_file_name = basename(args.registry_out_file.name) config = { "driver_config": { "device_address": str(target_address), "device_id": device_id }, "driver_type": "bacnet", "registry_config": "config://registry_configs/{}".format(config_file_name) } json.dump(config, args.driver_out_file, indent=4) _log.debug('pduSource = ' + target_address) _log.debug('iAmDeviceIdentifier = ' + str(device_id)) _log.debug('maxAPDULengthAccepted = ' + str(results["max_apdu_length"])) _log.debug('segmentationSupported = ' + results["segmentation_supported"]) _log.debug('vendorID = ' + str(results["vendor_id"])) try: device_name = read_prop(target_address, "device", device_id, "objectName") _log.debug('device_name = ' + str(device_name)) except TypeError: _log.debug('device missing objectName') try: device_description = read_prop(target_address, "device", device_id, "description") _log.debug('description = ' + str(device_description)) except TypeError: _log.debug('device missing description') config_writer = DictWriter( args.registry_out_file, ('Reference Point Name', 'Volttron Point Name', 'Units', 'Unit Details', 'BACnet Object Type', 'Property', 'Writable', 'Index', 'Write Priority', 'Notes')) config_writer.writeheader() try: object_count = read_prop(target_address, "device", device_id, "objectList", index=0) list_property = "objectList" except TypeError: object_count = read_prop(target_address, "device", device_id, "structuredObjectList", index=0) list_property = "structuredObjectList" _log.debug('object_count = ' + str(object_count)) for object_index in xrange(1, object_count + 1): _log.debug('object_device_index = ' + repr(object_index)) bac_object = read_prop(target_address, "device", device_id, list_property, index=object_index) obj_type, index = bac_object process_object(target_address, obj_type, index, args.max_range_report, config_writer)
import logging import os import gevent from volttron.platform import get_address from volttron.platform.agent import utils from volttron.platform.keystore import KeyStore from volttron.platform.vip.agent import Agent from volttron.platform.vip.agent.connection import Connection utils.setup_logging() _log = logging.getLogger(__name__) ks = KeyStore() def build_connection(identity, peer='', address=get_address(), publickey=ks.public, secretkey=ks.secret, **kwargs): cn = Connection(address=address, identity=identity, peer=peer, publickey=publickey, secretkey=secretkey, **kwargs) return cn
def main(): # parse the command line arguments arg_parser = argparse.ArgumentParser(description=__doc__) arg_parser.add_argument( "--address", help="Target only device(s) at <address> for request") arg_parser.add_argument( "--range", type=int, nargs=2, metavar=('LOW', 'HIGH'), help="Lower and upper limit on device ID in results") arg_parser.add_argument( "--timeout", type=int, metavar=('SECONDS'), help="Time, in seconds, to wait for responses. Default: %(default)s", default=5) arg_parser.add_argument("--proxy-id", help="VIP IDENTITY of the BACnet proxy agent.", default="platform.bacnet_proxy") arg_parser.add_argument("--csv-out", dest="csv_out", help="Write results to the CSV file specified.") arg_parser.add_argument("--debug", action="store_true", help="Set the logger in debug mode") args = arg_parser.parse_args() core_logger = logging.getLogger("volttron.platform.vip.agent.core") core_logger.setLevel(logging.WARN) _log.setLevel(logging.WARN) if args.debug: _log.setLevel(logging.DEBUG) core_logger.setLevel(logging.DEBUG) _log.debug("initialization") _log.debug(" - args: %r", args) csv_writer = None if args.csv_out is not None: f = open(args.csv_out, "wb") field_names = [ "address", "device_id", "max_apdu_length", "segmentation_supported", "vendor_id" ] csv_writer = csv.DictWriter(f, field_names) csv_writer.writeheader() keystore = KeyStore() agent = BACnetInteraction(args.proxy_id, csv_writer=csv_writer, address=get_address(), volttron_home=get_home(), publickey=keystore.public, secretkey=keystore.secret, enable_store=False) event = gevent.event.Event() gevent.spawn(agent.core.run, event) event.wait() kwargs = {'address': args.address} if args.range is not None: kwargs['low_device_id'] = int(args.range[0]) kwargs['high_device_id'] = int(args.range[1]) try: agent.send_iam(**kwargs) except errors.Unreachable: _log.error( "There is no BACnet proxy Agent running on the platform with the VIP IDENTITY {}" .format(args.proxy_id)) else: gevent.sleep(args.timeout)